Skip to content

Commit

Permalink
Clean up rtmp extension
Browse files Browse the repository at this point in the history
  • Loading branch information
ojw28 committed Jul 5, 2017
1 parent 1279b7d commit 7524228
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 61 deletions.
2 changes: 2 additions & 0 deletions core_settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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) {
Expand Down
39 changes: 16 additions & 23 deletions extensions/rtmp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,26 @@

## Description ##

The RTMP Extension is an [DataSource][] implementation for playing [RTMP][] streaming using
[Librtmp Client for Android].
The RTMP Extension is a [DataSource][] implementation for playing [RTMP][]
streams using [LibRtmp Client for Android][].

## Using the extension ##

When building [MediaSource][], inject `RtmpDataSourceFactory` like this:
[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

```java
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
int type = TextUtils.isEmpty(overrideExtension) ? Util.inferContentType(uri)
: Util.inferContentType("." + overrideExtension);
switch (type) {
## Using the extension ##

// ... other types cases
The easiest way to use the extension is to add it as a gradle dependency:

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);
}
}
}
```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.

[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
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
9 changes: 6 additions & 3 deletions extensions/rtmp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@
// 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 {
compileSdkVersion project.ext.compileSdkVersion
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'
}
2 changes: 1 addition & 1 deletion extensions/rtmp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,55 +16,77 @@
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() {
rtmpClient = new RtmpClient();
this(null);
}

@Override
public Uri getUri() {
return uri;
/**
* @param listener An optional listener.
*/
public RtmpDataSource(@Nullable TransferListener<? super RtmpDataSource> listener) {
this.listener = listener;
}

@Override
public long open(DataSpec dataSpec) throws IOException {
uri = dataSpec.uri;
int result = rtmpClient.open(dataSpec.uri.toString(), false);
if (result < 0) {
return 0;
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 void close() throws IOException {
rtmpClient.close();
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 int read(byte[] buffer, int offset, int readLength) throws IOException {
return rtmpClient.read(buffer, offset, readLength);
public void close() {
if (uri != null) {
uri = null;
if (listener != null) {
listener.onTransferEnd(this);
}
}
if (rtmpClient != null) {
rtmpClient.close();
rtmpClient = null;
}
}

public final static class RtmpDataSourceFactory implements DataSource.Factory {
@Override
public DataSource createDataSource() {
return new RtmpDataSource();
}
@Override
public Uri getUri() {
return uri;
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@

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:
Expand All @@ -30,6 +32,8 @@
* 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
Expand All @@ -38,13 +42,22 @@
*/
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;

Expand Down Expand Up @@ -95,10 +108,9 @@ public DefaultDataSource(Context context, TransferListener<? super DataSource> l
*/
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
Expand All @@ -108,14 +120,16 @@ public long open(DataSpec dataSpec) throws IOException {
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;
}
Expand Down Expand Up @@ -144,4 +158,48 @@ public void close() throws IOException {
}
}

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;
}

}

0 comments on commit 7524228

Please sign in to comment.