Clean up rtmp extension
This commit is contained in:
parent
1279b7dcf1
commit
7524228160
@ -31,6 +31,7 @@ include modulePrefix + 'extension-ima'
|
|||||||
include modulePrefix + 'extension-okhttp'
|
include modulePrefix + 'extension-okhttp'
|
||||||
include modulePrefix + 'extension-opus'
|
include modulePrefix + 'extension-opus'
|
||||||
include modulePrefix + 'extension-vp9'
|
include modulePrefix + 'extension-vp9'
|
||||||
|
include modulePrefix + 'extension-rtmp'
|
||||||
|
|
||||||
project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all')
|
project(modulePrefix + 'library').projectDir = new File(rootDir, 'library/all')
|
||||||
project(modulePrefix + 'library-core').projectDir = new File(rootDir, 'library/core')
|
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-okhttp').projectDir = new File(rootDir, 'extensions/okhttp')
|
||||||
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus')
|
project(modulePrefix + 'extension-opus').projectDir = new File(rootDir, 'extensions/opus')
|
||||||
project(modulePrefix + 'extension-vp9').projectDir = new File(rootDir, 'extensions/vp9')
|
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')
|
if (gradle.ext.has('exoplayerIncludeCronetExtension')
|
||||||
&& gradle.ext.exoplayerIncludeCronetExtension) {
|
&& gradle.ext.exoplayerIncludeCronetExtension) {
|
||||||
|
@ -2,33 +2,26 @@
|
|||||||
|
|
||||||
## Description ##
|
## Description ##
|
||||||
|
|
||||||
The RTMP Extension is an [DataSource][] implementation for playing [RTMP][] streaming using
|
The RTMP Extension is a [DataSource][] implementation for playing [RTMP][]
|
||||||
[Librtmp Client for Android].
|
streams 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
[DataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/DataSource.html
|
[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
|
[RTMP]: https://en.wikipedia.org/wiki/Real-Time_Messaging_Protocol
|
||||||
[Librtmp Client for Android]: https://github.com/ant-media/LibRtmp-Client-for-Android
|
[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
|
|
||||||
|
## 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
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
apply from: '../../constants.gradle'
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -18,12 +19,14 @@ android {
|
|||||||
buildToolsVersion project.ext.buildToolsVersion
|
buildToolsVersion project.ext.buildToolsVersion
|
||||||
|
|
||||||
defaultConfig {
|
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
|
targetSdkVersion project.ext.targetSdkVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':library-core')
|
compile project(':library-core')
|
||||||
compile 'net.butterflytv.utils:rtmp-client:0.2.6.1'
|
compile 'net.butterflytv.utils:rtmp-client:0.2.7.1'
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package com.google.android.exoplayer2.ext.rtmp;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import net.butterflytv.rtmp_client.RtmpClient;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
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}.
|
* A Real-Time Messaging Protocol (RTMP) {@link DataSource}.
|
||||||
*/
|
*/
|
||||||
public final class RtmpDataSource implements DataSource {
|
public final class RtmpDataSource implements DataSource {
|
||||||
private final RtmpClient rtmpClient;
|
|
||||||
|
@Nullable private final TransferListener<? super RtmpDataSource> listener;
|
||||||
|
|
||||||
|
private RtmpClient rtmpClient;
|
||||||
private Uri uri;
|
private Uri uri;
|
||||||
|
|
||||||
public RtmpDataSource() {
|
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 = 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
|
@Override
|
||||||
@ -41,30 +89,4 @@ public final class RtmpDataSource implements DataSource {
|
|||||||
return uri;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,9 +17,11 @@ package com.google.android.exoplayer2.upstream;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link DataSource} that supports multiple URI schemes. The supported schemes are:
|
* A {@link DataSource} that supports multiple URI schemes. The supported schemes are:
|
||||||
@ -30,6 +32,8 @@ import java.io.IOException;
|
|||||||
* local file URI).
|
* local file URI).
|
||||||
* <li>asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4).
|
* <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>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
|
* <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
|
* constructed using {@link #DefaultDataSource(Context, TransferListener, String, boolean)}, or
|
||||||
* any other schemes supported by a base data source if constructed using
|
* 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 {
|
public final class DefaultDataSource implements DataSource {
|
||||||
|
|
||||||
|
private static final String TAG = "DefaultDataSource";
|
||||||
|
|
||||||
private static final String SCHEME_ASSET = "asset";
|
private static final String SCHEME_ASSET = "asset";
|
||||||
private static final String SCHEME_CONTENT = "content";
|
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 baseDataSource;
|
||||||
private final DataSource fileDataSource;
|
|
||||||
private final DataSource assetDataSource;
|
// Lazily initialized.
|
||||||
private final DataSource contentDataSource;
|
private DataSource fileDataSource;
|
||||||
|
private DataSource assetDataSource;
|
||||||
|
private DataSource contentDataSource;
|
||||||
|
private DataSource rtmpDataSource;
|
||||||
|
|
||||||
private DataSource dataSource;
|
private DataSource dataSource;
|
||||||
|
|
||||||
@ -95,10 +108,9 @@ public final class DefaultDataSource implements DataSource {
|
|||||||
*/
|
*/
|
||||||
public DefaultDataSource(Context context, TransferListener<? super DataSource> listener,
|
public DefaultDataSource(Context context, TransferListener<? super DataSource> listener,
|
||||||
DataSource baseDataSource) {
|
DataSource baseDataSource) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
this.listener = listener;
|
||||||
this.baseDataSource = Assertions.checkNotNull(baseDataSource);
|
this.baseDataSource = Assertions.checkNotNull(baseDataSource);
|
||||||
this.fileDataSource = new FileDataSource(listener);
|
|
||||||
this.assetDataSource = new AssetDataSource(context, listener);
|
|
||||||
this.contentDataSource = new ContentDataSource(context, listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -108,14 +120,16 @@ public final class DefaultDataSource implements DataSource {
|
|||||||
String scheme = dataSpec.uri.getScheme();
|
String scheme = dataSpec.uri.getScheme();
|
||||||
if (Util.isLocalFileUri(dataSpec.uri)) {
|
if (Util.isLocalFileUri(dataSpec.uri)) {
|
||||||
if (dataSpec.uri.getPath().startsWith("/android_asset/")) {
|
if (dataSpec.uri.getPath().startsWith("/android_asset/")) {
|
||||||
dataSource = assetDataSource;
|
dataSource = getAssetDataSource();
|
||||||
} else {
|
} else {
|
||||||
dataSource = fileDataSource;
|
dataSource = getFileDataSource();
|
||||||
}
|
}
|
||||||
} else if (SCHEME_ASSET.equals(scheme)) {
|
} else if (SCHEME_ASSET.equals(scheme)) {
|
||||||
dataSource = assetDataSource;
|
dataSource = getAssetDataSource();
|
||||||
} else if (SCHEME_CONTENT.equals(scheme)) {
|
} else if (SCHEME_CONTENT.equals(scheme)) {
|
||||||
dataSource = contentDataSource;
|
dataSource = getContentDataSource();
|
||||||
|
} else if (SCHEME_RTMP.equals(scheme)) {
|
||||||
|
dataSource = getRtmpDataSource();
|
||||||
} else {
|
} else {
|
||||||
dataSource = baseDataSource;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user