Clean up rtmp extension

This commit is contained in:
Oliver Woodman 2017-07-05 15:08:00 +01:00
parent 1279b7dcf1
commit 7524228160
7 changed files with 198 additions and 73 deletions

View File

@ -31,6 +31,7 @@ include modulePrefix + 'extension-ima'
include modulePrefix + 'extension-okhttp'
include modulePrefix + 'extension-opus'
include modulePrefix + 'extension-vp9'
include modulePrefix + 'extension-rtmp'
project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all')
project(modulePrefix + 'library-core').projectDir = new File(rootDir, 'library/core')
@ -46,6 +47,7 @@ project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensio
project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'extensions/okhttp')
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus')
project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9')
project(modulePrefix + 'extension-rtmp').projectDir = new File(rootDir, 'extensions/rtmp')
if (gradle.ext.has('exoplayerIncludeCronetExtension')
&& gradle.ext.exoplayerIncludeCronetExtension) {

View File

@ -2,33 +2,26 @@
## Description ##
The RTMP Extension is an [DataSource][] implementation for playing [RTMP][] streaming using
[Librtmp Client for Android].
## Using the extension ##
When building [MediaSource][], inject `RtmpDataSourceFactory` like this:
```java
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri)
: Util.inferContentType("." + overrideExtension);
switch (type) {
// ... other types cases
case C.TYPE_OTHER:
DataSource.Factory factory = uri.getScheme().equals("rtmp") ? new RtmpDataSourceFactory() : mediaDataSourceFactory;
return new ExtractorMediaSource(uri, factory, new DefaultExtractorsFactory(), mainHandler, eventLogger);
default: {
throw new IllegalStateException("Unsupported type: " + type);
}
}
}
```
The RTMP Extension is a [DataSource][] implementation for playing [RTMP][]
streams using [LibRtmp Client for Android][].
[DataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/DataSource.html
[RTMP]: https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol
[Librtmp Client for Android]: https://github.com/ant-media/LibRtmp-Client-for-Android
[MediaSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/source/MediaSource.html
[LibRtmp Client for Android]: https://github.com/ant-media/LibRtmp-Client-for-Android
## Using the extension ##
The easiest way to use the extension is to add it as a gradle dependency:
```gradle
compile 'com.google.android.exoplayer:extension-rtmp:rX.X.X'
```
where `rX.X.X` is the version, which must match the version of the ExoPlayer
library being used.
Alternatively, you can clone the ExoPlayer repository and depend on the module
locally. Instructions for doing this can be found in ExoPlayer's
[top level README][].
[top level README]: https://github.com/google/ExoPlayer/blob/release-v2/README.md

View File

@ -11,6 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: '../../constants.gradle'
apply plugin: 'com.android.library'
android {
@ -18,12 +19,14 @@ android {
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
minSdkVersion 16
// TODO: Lower minSdkVersion as much as possible once this issue in LibRtmp is fixed:
// https://github.com/ant-media/LibRtmp-Client-for-Android/issues/39
minSdkVersion 21
targetSdkVersion project.ext.targetSdkVersion
}
}
dependencies {
compile project(':library-core')
compile 'net.butterflytv.utils:rtmp-client:0.2.6.1'
}
compile 'net.butterflytv.utils:rtmp-client:0.2.7.1'
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
<!-- Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 The Android Open Source Project
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,24 +16,72 @@
package com.google.android.exoplayer2.ext.rtmp;
import android.net.Uri;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import net.butterflytv.rtmp_client.RtmpClient;
import com.google.android.exoplayer2.upstream.TransferListener;
import java.io.IOException;
import net.butterflytv.rtmp_client.RtmpClient;
import net.butterflytv.rtmp_client.RtmpClient.RtmpIOException;
/**
* A Real-Time Messaging Protocol (RTMP) {@link DataSource}.
*/
public final class RtmpDataSource implements DataSource {
private final RtmpClient rtmpClient;
@Nullable private final TransferListener<? super RtmpDataSource> listener;
private RtmpClient rtmpClient;
private Uri uri;
public RtmpDataSource() {
this(null);
}
/**
* @param listener An optional listener.
*/
public RtmpDataSource(@Nullable TransferListener<? super RtmpDataSource> listener) {
this.listener = listener;
}
@Override
public long open(DataSpec dataSpec) throws RtmpIOException {
rtmpClient = new RtmpClient();
rtmpClient.open(dataSpec.uri.toString(), false);
this.uri = dataSpec.uri;
if (listener != null) {
listener.onTransferStart(this, dataSpec);
}
return C.LENGTH_UNSET;
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
int bytesRead = rtmpClient.read(buffer, offset, readLength);
if (bytesRead == -1) {
return C.RESULT_END_OF_INPUT;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
}
return bytesRead;
}
@Override
public void close() {
if (uri != null) {
uri = null;
if (listener != null) {
listener.onTransferEnd(this);
}
}
if (rtmpClient != null) {
rtmpClient.close();
rtmpClient = null;
}
}
@Override
@ -41,30 +89,4 @@ public final class RtmpDataSource implements DataSource {
return uri;
}
@Override
public long open(DataSpec dataSpec) throws IOException {
uri = dataSpec.uri;
int result = rtmpClient.open(dataSpec.uri.toString(), false);
if (result < 0) {
return 0;
}
return C.LENGTH_UNSET;
}
@Override
public void close() throws IOException {
rtmpClient.close();
}
@Override
public int read(byte[] buffer, int offset, int readLength) throws IOException {
return rtmpClient.read(buffer, offset, readLength);
}
public final static class RtmpDataSourceFactory implements DataSource.Factory {
@Override
public DataSource createDataSource() {
return new RtmpDataSource();
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.rtmp;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.Factory;
import com.google.android.exoplayer2.upstream.TransferListener;
/**
* A {@link Factory} that produces {@link RtmpDataSource}.
*/
public final class RtmpDataSourceFactory implements DataSource.Factory {
@Nullable
private final TransferListener<? super RtmpDataSource> listener;
public RtmpDataSourceFactory() {
this(null);
}
/**
* @param listener An optional listener.
*/
public RtmpDataSourceFactory(@Nullable TransferListener<? super RtmpDataSource> listener) {
this.listener = listener;
}
@Override
public DataSource createDataSource() {
return new RtmpDataSource(listener);
}
}

View File

@ -17,9 +17,11 @@ package com.google.android.exoplayer2.upstream;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
/**
* A {@link DataSource} that supports multiple URI schemes. The supported schemes are:
@ -30,6 +32,8 @@ import java.io.IOException;
* local file URI).
* <li>asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4).
* <li>content: For fetching data from a content URI (e.g. content://authority/path/123).
* <li>rtmp: For fetching data over RTMP. Only supported if the project using ExoPlayer has an
* explicit dependency on ExoPlayer's RTMP extension.</li>
* <li>http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4), if
* constructed using {@link #DefaultDataSource(Context, TransferListener, String, boolean)}, or
* any other schemes supported by a base data source if constructed using
@ -38,13 +42,22 @@ import java.io.IOException;
*/
public final class DefaultDataSource implements DataSource {
private static final String TAG = "DefaultDataSource";
private static final String SCHEME_ASSET = "asset";
private static final String SCHEME_CONTENT = "content";
private static final String SCHEME_RTMP = "rtmp";
private final Context context;
private final TransferListener<? super DataSource> listener;
private final DataSource baseDataSource;
private final DataSource fileDataSource;
private final DataSource assetDataSource;
private final DataSource contentDataSource;
// Lazily initialized.
private DataSource fileDataSource;
private DataSource assetDataSource;
private DataSource contentDataSource;
private DataSource rtmpDataSource;
private DataSource dataSource;
@ -95,10 +108,9 @@ public final class DefaultDataSource implements DataSource {
*/
public DefaultDataSource(Context context, TransferListener<? super DataSource> listener,
DataSource baseDataSource) {
this.context = context.getApplicationContext();
this.listener = listener;
this.baseDataSource = Assertions.checkNotNull(baseDataSource);
this.fileDataSource = new FileDataSource(listener);
this.assetDataSource = new AssetDataSource(context, listener);
this.contentDataSource = new ContentDataSource(context, listener);
}
@Override
@ -108,14 +120,16 @@ public final class DefaultDataSource implements DataSource {
String scheme = dataSpec.uri.getScheme();
if (Util.isLocalFileUri(dataSpec.uri)) {
if (dataSpec.uri.getPath().startsWith("/android_asset/")) {
dataSource = assetDataSource;
dataSource = getAssetDataSource();
} else {
dataSource = fileDataSource;
dataSource = getFileDataSource();
}
} else if (SCHEME_ASSET.equals(scheme)) {
dataSource = assetDataSource;
dataSource = getAssetDataSource();
} else if (SCHEME_CONTENT.equals(scheme)) {
dataSource = contentDataSource;
dataSource = getContentDataSource();
} else if (SCHEME_RTMP.equals(scheme)) {
dataSource = getRtmpDataSource();
} else {
dataSource = baseDataSource;
}
@ -144,4 +158,48 @@ public final class DefaultDataSource implements DataSource {
}
}
private DataSource getFileDataSource() {
if (fileDataSource == null) {
fileDataSource = new FileDataSource(listener);
}
return fileDataSource;
}
private DataSource getAssetDataSource() {
if (assetDataSource == null) {
assetDataSource = new AssetDataSource(context, listener);
}
return assetDataSource;
}
private DataSource getContentDataSource() {
if (contentDataSource == null) {
contentDataSource = new ContentDataSource(context, listener);
}
return contentDataSource;
}
private DataSource getRtmpDataSource() {
if (rtmpDataSource == null) {
try {
Class<?> clazz = Class.forName("com.google.android.exoplayer2.ext.rtmp.RtmpDataSource");
rtmpDataSource = (DataSource) clazz.getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException e) {
Log.w(TAG, "Attempting to play RTMP stream without depending on the RTMP extension");
} catch (InstantiationException e) {
Log.e(TAG, "Error instantiating RtmpDataSource", e);
} catch (IllegalAccessException e) {
Log.e(TAG, "Error instantiating RtmpDataSource", e);
} catch (NoSuchMethodException e) {
Log.e(TAG, "Error instantiating RtmpDataSource", e);
} catch (InvocationTargetException e) {
Log.e(TAG, "Error instantiating RtmpDataSource", e);
}
if (rtmpDataSource == null) {
rtmpDataSource = baseDataSource;
}
}
return rtmpDataSource;
}
}