Creating an leanback extension for ExoPlayer. Leanback added support for new
transport control, which allows developers to plug in any media player. This extension provides the PlayerAdapter implementation for ExoPlayer. Demo: https://drive.google.com/open?id=0B1GHUu5ruGULZTJVV1pVNlBuVjQ ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=165183497
This commit is contained in:
parent
cf6534ea5b
commit
c9393db878
@ -33,6 +33,7 @@ include modulePrefix + 'extension-okhttp'
|
|||||||
include modulePrefix + 'extension-opus'
|
include modulePrefix + 'extension-opus'
|
||||||
include modulePrefix + 'extension-vp9'
|
include modulePrefix + 'extension-vp9'
|
||||||
include modulePrefix + 'extension-rtmp'
|
include modulePrefix + 'extension-rtmp'
|
||||||
|
include modulePrefix + 'extension-leanback'
|
||||||
|
|
||||||
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')
|
||||||
@ -50,6 +51,7 @@ project(modulePrefix + 'extension-okhttp').projectDir = new File(rootDir, 'exten
|
|||||||
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')
|
project(modulePrefix + 'extension-rtmp').projectDir = new File(rootDir, 'extensions/rtmp')
|
||||||
|
project(modulePrefix + 'extension-leanback').projectDir = new File(rootDir, 'extensions/leanback')
|
||||||
|
|
||||||
if (gradle.ext.has('exoplayerIncludeCronetExtension')
|
if (gradle.ext.has('exoplayerIncludeCronetExtension')
|
||||||
&& gradle.ext.exoplayerIncludeCronetExtension) {
|
&& gradle.ext.exoplayerIncludeCronetExtension) {
|
||||||
|
26
extensions/leanback/README.md
Normal file
26
extensions/leanback/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# ExoPlayer Leanback Extension #
|
||||||
|
|
||||||
|
## Description ##
|
||||||
|
|
||||||
|
This [Leanback][] Extension provides a [PlayerAdapter][] implementation for
|
||||||
|
Exoplayer.
|
||||||
|
|
||||||
|
[PlayerAdapter]: https://developer.android.com/reference/android/support/v17/leanback/media/PlayerAdapter.html
|
||||||
|
[Leanback]: https://developer.android.com/reference/android/support/v17/leanback/package-summary.html
|
||||||
|
|
||||||
|
## Getting the extension ##
|
||||||
|
|
||||||
|
The easiest way to use the extension is to add it as a gradle dependency:
|
||||||
|
|
||||||
|
```gradle
|
||||||
|
compile 'com.google.android.exoplayer:extension-leanback: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
|
41
extensions/leanback/build.gradle
Normal file
41
extensions/leanback/build.gradle
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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.
|
||||||
|
apply from: '../../constants.gradle'
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
buildToolsVersion project.ext.buildToolsVersion
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion project.ext.minSdkVersion
|
||||||
|
targetSdkVersion project.ext.targetSdkVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(modulePrefix + 'library-core')
|
||||||
|
compile('com.android.support:leanback-v17:' + supportLibraryVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
javadocTitle = 'Leanback extension for Exoplayer library'
|
||||||
|
}
|
||||||
|
apply from: '../../javadoc_library.gradle'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
releaseArtifact = 'extension-leanback'
|
||||||
|
releaseDescription = 'Leanback extension for ExoPlayer.'
|
||||||
|
}
|
||||||
|
apply from: '../../publish.gradle'
|
17
extensions/leanback/src/main/AndroidManifest.xml
Normal file
17
extensions/leanback/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest package="com.google.android.exoplayer2.ext.leanback"/>
|
@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* 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.leanback;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.v17.leanback.R;
|
||||||
|
import android.support.v17.leanback.media.PlaybackGlueHost;
|
||||||
|
import android.support.v17.leanback.media.PlayerAdapter;
|
||||||
|
import android.support.v17.leanback.media.SurfaceHolderGlueHost;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
import com.google.android.exoplayer2.util.ErrorMessageProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leanback {@link PlayerAdapter} implementation for {@link SimpleExoPlayer}.
|
||||||
|
*/
|
||||||
|
public final class LeanbackPlayerAdapter extends PlayerAdapter {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final SimpleExoPlayer player;
|
||||||
|
private final Handler handler;
|
||||||
|
private final Runnable updatePlayerRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
getCallback().onCurrentPositionChanged(LeanbackPlayerAdapter.this);
|
||||||
|
getCallback().onBufferedPositionChanged(LeanbackPlayerAdapter.this);
|
||||||
|
handler.postDelayed(this, updatePeriod);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private SurfaceHolderGlueHost surfaceHolderGlueHost;
|
||||||
|
private boolean initialized;
|
||||||
|
private boolean hasDisplay;
|
||||||
|
private boolean isBuffering;
|
||||||
|
private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider;
|
||||||
|
private final int updatePeriod;
|
||||||
|
private final ExoPlayerEventListenerImpl exoPlayerListener = new ExoPlayerEventListenerImpl();
|
||||||
|
private final SimpleExoPlayer.VideoListener videoListener = new SimpleExoPlayer.VideoListener() {
|
||||||
|
@Override
|
||||||
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees,
|
||||||
|
float pixelWidthHeightRatio) {
|
||||||
|
getCallback().onVideoSizeChanged(LeanbackPlayerAdapter.this, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRenderedFirstFrame() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static {
|
||||||
|
ExoPlayerLibraryInfo.registerModule("goog.exo.leanback");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
* Users are responsible for managing {@link SimpleExoPlayer} lifecycle. You must
|
||||||
|
* stop/release the player once you're done playing the media.
|
||||||
|
*
|
||||||
|
* @param context The current context (activity).
|
||||||
|
* @param player Instance of your exoplayer that needs to be configured.
|
||||||
|
*/
|
||||||
|
public LeanbackPlayerAdapter(Context context, SimpleExoPlayer player, int updatePeriod) {
|
||||||
|
this.context = context;
|
||||||
|
this.player = player;
|
||||||
|
this.handler = new Handler();
|
||||||
|
this.updatePeriod = updatePeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachedToHost(PlaybackGlueHost host) {
|
||||||
|
if (host instanceof SurfaceHolderGlueHost) {
|
||||||
|
surfaceHolderGlueHost = ((SurfaceHolderGlueHost) host);
|
||||||
|
surfaceHolderGlueHost.setSurfaceHolderCallback(new VideoPlayerSurfaceHolderCallback());
|
||||||
|
}
|
||||||
|
initializePlayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializePlayer() {
|
||||||
|
notifyListeners();
|
||||||
|
this.player.addListener(exoPlayerListener);
|
||||||
|
this.player.setVideoListener(videoListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListeners() {
|
||||||
|
boolean oldIsPrepared = isPrepared();
|
||||||
|
int playbackState = player.getPlaybackState();
|
||||||
|
boolean isInitialized = playbackState != ExoPlayer.STATE_IDLE;
|
||||||
|
isBuffering = playbackState == ExoPlayer.STATE_BUFFERING;
|
||||||
|
boolean hasEnded = playbackState == ExoPlayer.STATE_ENDED;
|
||||||
|
|
||||||
|
initialized = isInitialized;
|
||||||
|
if (oldIsPrepared != isPrepared()) {
|
||||||
|
getCallback().onPreparedStateChanged(LeanbackPlayerAdapter.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCallback().onPlayStateChanged(this);
|
||||||
|
notifyBufferingState();
|
||||||
|
|
||||||
|
if (hasEnded) {
|
||||||
|
getCallback().onPlayCompleted(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the optional {@link ErrorMessageProvider}.
|
||||||
|
*
|
||||||
|
* @param errorMessageProvider The {@link ErrorMessageProvider}.
|
||||||
|
*/
|
||||||
|
public void setErrorMessageProvider(
|
||||||
|
ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) {
|
||||||
|
this.errorMessageProvider = errorMessageProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uninitializePlayer() {
|
||||||
|
if (initialized) {
|
||||||
|
initialized = false;
|
||||||
|
notifyBufferingState();
|
||||||
|
if (hasDisplay) {
|
||||||
|
getCallback().onPlayStateChanged(LeanbackPlayerAdapter.this);
|
||||||
|
getCallback().onPreparedStateChanged(LeanbackPlayerAdapter.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.removeListener(exoPlayerListener);
|
||||||
|
player.clearVideoListener(videoListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the state of buffering. For example, an app may enable/disable a loading figure
|
||||||
|
* according to the state of buffering.
|
||||||
|
*/
|
||||||
|
private void notifyBufferingState() {
|
||||||
|
getCallback().onBufferingStateChanged(LeanbackPlayerAdapter.this,
|
||||||
|
isBuffering || !initialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachedFromHost() {
|
||||||
|
if (surfaceHolderGlueHost != null) {
|
||||||
|
surfaceHolderGlueHost.setSurfaceHolderCallback(null);
|
||||||
|
surfaceHolderGlueHost = null;
|
||||||
|
}
|
||||||
|
uninitializePlayer();
|
||||||
|
hasDisplay = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProgressUpdatingEnabled(final boolean enabled) {
|
||||||
|
handler.removeCallbacks(updatePlayerRunnable);
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handler.postDelayed(updatePlayerRunnable, updatePeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlaying() {
|
||||||
|
return initialized && player.getPlayWhenReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDuration() {
|
||||||
|
long duration = player.getDuration();
|
||||||
|
return duration != C.TIME_UNSET ? duration : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return initialized ? player.getCurrentPosition() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void play() {
|
||||||
|
if (player.getPlaybackState() == ExoPlayer.STATE_ENDED) {
|
||||||
|
seekTo(0);
|
||||||
|
}
|
||||||
|
player.setPlayWhenReady(true);
|
||||||
|
getCallback().onPlayStateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pause() {
|
||||||
|
player.setPlayWhenReady(false);
|
||||||
|
getCallback().onPlayStateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(long newPosition) {
|
||||||
|
player.seekTo(newPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBufferedPosition() {
|
||||||
|
return player.getBufferedPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return True if ExoPlayer is ready and got a SurfaceHolder if
|
||||||
|
* {@link PlaybackGlueHost} provides SurfaceHolder.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isPrepared() {
|
||||||
|
return initialized && (surfaceHolderGlueHost == null || hasDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see SimpleExoPlayer#setVideoSurfaceHolder(SurfaceHolder)
|
||||||
|
*/
|
||||||
|
private void setDisplay(SurfaceHolder surfaceHolder) {
|
||||||
|
hasDisplay = surfaceHolder != null;
|
||||||
|
player.setVideoSurface(surfaceHolder.getSurface());
|
||||||
|
getCallback().onPreparedStateChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link SurfaceHolder.Callback} that can then be set on the
|
||||||
|
* {@link PlaybackGlueHost}.
|
||||||
|
*/
|
||||||
|
private final class VideoPlayerSurfaceHolderCallback implements SurfaceHolder.Callback {
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder surfaceHolder) {
|
||||||
|
setDisplay(surfaceHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
|
||||||
|
setDisplay(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ExoPlayerEventListenerImpl implements ExoPlayer.EventListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||||
|
LeanbackPlayerAdapter.this.notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerError(ExoPlaybackException exception) {
|
||||||
|
String errMsg = "";
|
||||||
|
if (errorMessageProvider != null) {
|
||||||
|
Pair<Integer, String> message = errorMessageProvider.getErrorMessage(exception);
|
||||||
|
if (message != null) {
|
||||||
|
getCallback().onError(LeanbackPlayerAdapter.this,
|
||||||
|
message.first,
|
||||||
|
message.second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getCallback().onError(LeanbackPlayerAdapter.this,
|
||||||
|
exception.type,
|
||||||
|
context.getString(R.string.lb_media_player_error,
|
||||||
|
exception.type,
|
||||||
|
exception.rendererIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingChanged(boolean isLoading) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
||||||
|
getCallback().onDurationChanged(LeanbackPlayerAdapter.this);
|
||||||
|
getCallback().onCurrentPositionChanged(LeanbackPlayerAdapter.this);
|
||||||
|
getCallback().onBufferedPositionChanged(LeanbackPlayerAdapter.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity() {
|
||||||
|
getCallback().onCurrentPositionChanged(LeanbackPlayerAdapter.this);
|
||||||
|
getCallback().onBufferedPositionChanged(LeanbackPlayerAdapter.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParametersChanged(PlaybackParameters params) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRepeatModeChanged(int repeatMode) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user