Allow easier ExoPlayer/Cast integration
This CL adds the fundamental pieces for ExoPlayer/Cast integration and includes a demo app to showcase this functionality. However, media queues should be supported in the first release of this extension. Issue:#2283 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=165576892
This commit is contained in:
parent
1cfd246cd3
commit
04d76fa8fc
@ -22,6 +22,7 @@ project.ext {
|
|||||||
buildToolsVersion = '26'
|
buildToolsVersion = '26'
|
||||||
testSupportLibraryVersion = '0.5'
|
testSupportLibraryVersion = '0.5'
|
||||||
supportLibraryVersion = '26.0.1'
|
supportLibraryVersion = '26.0.1'
|
||||||
|
playServicesLibraryVersion = '11.0.2'
|
||||||
dexmakerVersion = '1.2'
|
dexmakerVersion = '1.2'
|
||||||
mockitoVersion = '1.9.5'
|
mockitoVersion = '1.9.5'
|
||||||
releaseVersion = 'r2.5.1'
|
releaseVersion = 'r2.5.1'
|
||||||
|
@ -28,6 +28,7 @@ include modulePrefix + 'extension-ffmpeg'
|
|||||||
include modulePrefix + 'extension-flac'
|
include modulePrefix + 'extension-flac'
|
||||||
include modulePrefix + 'extension-gvr'
|
include modulePrefix + 'extension-gvr'
|
||||||
include modulePrefix + 'extension-ima'
|
include modulePrefix + 'extension-ima'
|
||||||
|
include modulePrefix + 'extension-cast'
|
||||||
include modulePrefix + 'extension-mediasession'
|
include modulePrefix + 'extension-mediasession'
|
||||||
include modulePrefix + 'extension-okhttp'
|
include modulePrefix + 'extension-okhttp'
|
||||||
include modulePrefix + 'extension-opus'
|
include modulePrefix + 'extension-opus'
|
||||||
@ -46,6 +47,7 @@ project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'exten
|
|||||||
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac')
|
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac')
|
||||||
project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr')
|
project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr')
|
||||||
project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima')
|
project(modulePrefix + 'extension-ima').projectDir = new File(rootDir, 'extensions/ima')
|
||||||
|
project(modulePrefix + 'extension-cast').projectDir = new File(rootDir, 'extensions/cast')
|
||||||
project(modulePrefix + 'extension-mediasession').projectDir = new File(rootDir, 'extensions/mediasession')
|
project(modulePrefix + 'extension-mediasession').projectDir = new File(rootDir, 'extensions/mediasession')
|
||||||
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')
|
||||||
|
4
demos/cast/README.md
Normal file
4
demos/cast/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Cast demo application #
|
||||||
|
|
||||||
|
This folder contains a demo application that showcases ExoPlayer integration
|
||||||
|
with Google Cast.
|
51
demos/cast/build.gradle
Normal file
51
demos/cast/build.gradle
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// 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.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
buildToolsVersion project.ext.buildToolsVersion
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion project.ext.targetSdkVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
shrinkResources true
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
jniDebuggable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
// The demo app does not have translations.
|
||||||
|
disable 'MissingTranslation'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(modulePrefix + 'library-core')
|
||||||
|
compile project(modulePrefix + 'library-dash')
|
||||||
|
compile project(modulePrefix + 'library-hls')
|
||||||
|
compile project(modulePrefix + 'library-smoothstreaming')
|
||||||
|
compile project(modulePrefix + 'library-ui')
|
||||||
|
compile project(modulePrefix + 'extension-cast')
|
||||||
|
}
|
43
demos/cast/src/main/AndroidManifest.xml
Normal file
43
demos/cast/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?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 xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.google.android.exoplayer2.castdemo"
|
||||||
|
android:versionCode="0001"
|
||||||
|
android:versionName="0.0.1">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="26"/>
|
||||||
|
|
||||||
|
<application android:label="@string/application_name" android:icon="@mipmap/ic_launcher"
|
||||||
|
android:largeHeap="true" android:allowBackup="false">
|
||||||
|
|
||||||
|
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||||
|
android:value="com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider" />
|
||||||
|
|
||||||
|
<activity android:name="com.google.android.exoplayer2.castdemo.MainActivity"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
|
||||||
|
android:launchMode="singleTop" android:label="@string/application_name"
|
||||||
|
android:theme="@style/Theme.AppCompat">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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.castdemo;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.gms.cast.MediaInfo;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods and constants for the Cast demo application.
|
||||||
|
*/
|
||||||
|
/* package */ final class CastDemoUtil {
|
||||||
|
|
||||||
|
public static final String MIME_TYPE_DASH = "application/dash+xml";
|
||||||
|
public static final String MIME_TYPE_HLS = "application/vnd.apple.mpegurl";
|
||||||
|
public static final String MIME_TYPE_SS = "application/vnd.ms-sstr+xml";
|
||||||
|
public static final String MIME_TYPE_VIDEO_MP4 = MimeTypes.VIDEO_MP4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of samples available in the cast demo app.
|
||||||
|
*/
|
||||||
|
public static final List<Sample> SAMPLES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a media sample.
|
||||||
|
*/
|
||||||
|
public static final class Sample {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uri from which the media sample is obtained.
|
||||||
|
*/
|
||||||
|
public final String uri;
|
||||||
|
/**
|
||||||
|
* A descriptive name for the sample.
|
||||||
|
*/
|
||||||
|
public final String name;
|
||||||
|
/**
|
||||||
|
* The mime type of the media sample, as required by {@link MediaInfo#setContentType}.
|
||||||
|
*/
|
||||||
|
public final String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param uri See {@link #uri}.
|
||||||
|
* @param name See {@link #name}.
|
||||||
|
* @param type See {@link #type}.
|
||||||
|
*/
|
||||||
|
public Sample(String uri, String name, String type) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
// App samples.
|
||||||
|
ArrayList<Sample> samples = new ArrayList<>();
|
||||||
|
samples.add(new Sample("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
|
||||||
|
"DASH (clear,MP4,H264)", MIME_TYPE_DASH));
|
||||||
|
samples.add(new Sample("https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/"
|
||||||
|
+ "hls/TearsOfSteel.m3u8", "Tears of Steel (HLS)", MIME_TYPE_HLS));
|
||||||
|
samples.add(new Sample("https://html5demos.com/assets/dizzy.mp4", "Dizzy (MP4)",
|
||||||
|
MIME_TYPE_VIDEO_MP4));
|
||||||
|
|
||||||
|
|
||||||
|
SAMPLES = Collections.unmodifiableList(samples);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private CastDemoUtil() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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.castdemo;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.ext.cast.CastPlayer;
|
||||||
|
import com.google.android.exoplayer2.ui.PlaybackControlView;
|
||||||
|
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An activity that plays video using {@link SimpleExoPlayer} and {@link CastPlayer}.
|
||||||
|
*/
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private SimpleExoPlayerView simpleExoPlayerView;
|
||||||
|
private PlaybackControlView castControlView;
|
||||||
|
private PlayerManager playerManager;
|
||||||
|
|
||||||
|
// Activity lifecycle methods.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.main_activity);
|
||||||
|
|
||||||
|
simpleExoPlayerView = (SimpleExoPlayerView) findViewById(R.id.player_view);
|
||||||
|
simpleExoPlayerView.requestFocus();
|
||||||
|
|
||||||
|
castControlView = (PlaybackControlView) findViewById(R.id.cast_control_view);
|
||||||
|
|
||||||
|
ListView sampleList = (ListView) findViewById(R.id.sample_list);
|
||||||
|
sampleList.setAdapter(new SampleListAdapter());
|
||||||
|
sampleList.setOnItemClickListener(new SampleClickListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
super.onCreateOptionsMenu(menu);
|
||||||
|
getMenuInflater().inflate(R.menu.menu, menu);
|
||||||
|
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu,
|
||||||
|
R.id.media_route_menu_item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
if (Util.SDK_INT > 23) {
|
||||||
|
setupPlayerManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if ((Util.SDK_INT <= 23)) {
|
||||||
|
setupPlayerManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (Util.SDK_INT <= 23) {
|
||||||
|
releasePlayerManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (Util.SDK_INT > 23) {
|
||||||
|
releasePlayerManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activity input.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
|
// If the event was not handled then see if the player view can handle it.
|
||||||
|
return super.dispatchKeyEvent(event) || playerManager.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
|
private void setupPlayerManager() {
|
||||||
|
playerManager = new PlayerManager(simpleExoPlayerView, castControlView,
|
||||||
|
getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releasePlayerManager() {
|
||||||
|
playerManager.release();
|
||||||
|
playerManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// User controls.
|
||||||
|
|
||||||
|
private final class SampleListAdapter extends ArrayAdapter<CastDemoUtil.Sample> {
|
||||||
|
|
||||||
|
public SampleListAdapter() {
|
||||||
|
super(getApplicationContext(), android.R.layout.simple_list_item_1, CastDemoUtil.SAMPLES);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View view = super.getView(position, convertView, parent);
|
||||||
|
view.setBackgroundColor(Color.WHITE);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SampleClickListener implements AdapterView.OnItemClickListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (parent.getSelectedItemPosition() != position) {
|
||||||
|
CastDemoUtil.Sample currentSample = CastDemoUtil.SAMPLES.get(position);
|
||||||
|
playerManager.setCurrentSample(currentSample, 0, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* 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.castdemo;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
|
import com.google.android.exoplayer2.RenderersFactory;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.ext.cast.CastPlayer;
|
||||||
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
|
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
|
||||||
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource;
|
||||||
|
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||||
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
|
import com.google.android.exoplayer2.ui.PlaybackControlView;
|
||||||
|
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages players for the ExoPlayer/Cast integration app.
|
||||||
|
*/
|
||||||
|
/* package */ final class PlayerManager implements CastPlayer.SessionAvailabilityListener {
|
||||||
|
|
||||||
|
private static final int PLAYBACK_REMOTE = 1;
|
||||||
|
private static final int PLAYBACK_LOCAL = 2;
|
||||||
|
|
||||||
|
private static final String USER_AGENT = "ExoCastDemoPlayer";
|
||||||
|
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
|
||||||
|
private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY =
|
||||||
|
new DefaultHttpDataSourceFactory(USER_AGENT, BANDWIDTH_METER);
|
||||||
|
|
||||||
|
private final SimpleExoPlayerView exoPlayerView;
|
||||||
|
private final PlaybackControlView castControlView;
|
||||||
|
private final CastContext castContext;
|
||||||
|
private final SimpleExoPlayer exoPlayer;
|
||||||
|
private final CastPlayer castPlayer;
|
||||||
|
|
||||||
|
private int playbackLocation;
|
||||||
|
private CastDemoUtil.Sample currentSample;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param exoPlayerView The {@link SimpleExoPlayerView} for local playback.
|
||||||
|
* @param castControlView The {@link PlaybackControlView} to control remote playback.
|
||||||
|
* @param context A {@link Context}.
|
||||||
|
*/
|
||||||
|
public PlayerManager(SimpleExoPlayerView exoPlayerView, PlaybackControlView castControlView,
|
||||||
|
Context context) {
|
||||||
|
this.exoPlayerView = exoPlayerView;
|
||||||
|
this.castControlView = castControlView;
|
||||||
|
castContext = CastContext.getSharedInstance(context);
|
||||||
|
|
||||||
|
DefaultTrackSelector trackSelector = new DefaultTrackSelector(BANDWIDTH_METER);
|
||||||
|
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, null);
|
||||||
|
exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
|
||||||
|
exoPlayerView.setPlayer(exoPlayer);
|
||||||
|
|
||||||
|
castPlayer = new CastPlayer(castContext);
|
||||||
|
castPlayer.setSessionAvailabilityListener(this);
|
||||||
|
castControlView.setPlayer(castPlayer);
|
||||||
|
|
||||||
|
setPlaybackLocation(castPlayer.isCastSessionAvailable() ? PLAYBACK_REMOTE : PLAYBACK_LOCAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts playback of the given sample at the given position.
|
||||||
|
*
|
||||||
|
* @param currentSample The {@link CastDemoUtil} to play.
|
||||||
|
* @param positionMs The position at which playback should start.
|
||||||
|
* @param playWhenReady Whether the player should proceed when ready to do so.
|
||||||
|
*/
|
||||||
|
public void setCurrentSample(CastDemoUtil.Sample currentSample, long positionMs,
|
||||||
|
boolean playWhenReady) {
|
||||||
|
this.currentSample = currentSample;
|
||||||
|
if (playbackLocation == PLAYBACK_REMOTE) {
|
||||||
|
castPlayer.load(currentSample.name, currentSample.uri, currentSample.type, positionMs,
|
||||||
|
playWhenReady);
|
||||||
|
} else /* playbackLocation == PLAYBACK_LOCAL */ {
|
||||||
|
exoPlayer.setPlayWhenReady(playWhenReady);
|
||||||
|
exoPlayer.seekTo(positionMs);
|
||||||
|
exoPlayer.prepare(buildMediaSource(currentSample), true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a given {@link KeyEvent} to whichever view corresponds according to the current
|
||||||
|
* playback location.
|
||||||
|
*
|
||||||
|
* @param event The {@link KeyEvent}.
|
||||||
|
* @return Whether the event was handled by the target view.
|
||||||
|
*/
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
|
if (playbackLocation == PLAYBACK_REMOTE) {
|
||||||
|
return castControlView.dispatchKeyEvent(event);
|
||||||
|
} else /* playbackLocation == PLAYBACK_REMOTE */ {
|
||||||
|
return exoPlayerView.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the manager and the players that it holds.
|
||||||
|
*/
|
||||||
|
public void release() {
|
||||||
|
castPlayer.setSessionAvailabilityListener(null);
|
||||||
|
castPlayer.release();
|
||||||
|
exoPlayerView.setPlayer(null);
|
||||||
|
exoPlayer.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// CastPlayer.SessionAvailabilityListener implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCastSessionAvailable() {
|
||||||
|
setPlaybackLocation(PLAYBACK_REMOTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCastSessionUnavailable() {
|
||||||
|
setPlaybackLocation(PLAYBACK_LOCAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
|
private static MediaSource buildMediaSource(CastDemoUtil.Sample sample) {
|
||||||
|
Uri uri = Uri.parse(sample.uri);
|
||||||
|
switch (sample.type) {
|
||||||
|
case CastDemoUtil.MIME_TYPE_SS:
|
||||||
|
return new SsMediaSource(uri, DATA_SOURCE_FACTORY,
|
||||||
|
new DefaultSsChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
|
||||||
|
case CastDemoUtil.MIME_TYPE_DASH:
|
||||||
|
return new DashMediaSource(uri, DATA_SOURCE_FACTORY,
|
||||||
|
new DefaultDashChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
|
||||||
|
case CastDemoUtil.MIME_TYPE_HLS:
|
||||||
|
return new HlsMediaSource(uri, DATA_SOURCE_FACTORY, null, null);
|
||||||
|
case CastDemoUtil.MIME_TYPE_VIDEO_MP4:
|
||||||
|
return new ExtractorMediaSource(uri, DATA_SOURCE_FACTORY, new DefaultExtractorsFactory(),
|
||||||
|
null, null);
|
||||||
|
default: {
|
||||||
|
throw new IllegalStateException("Unsupported type: " + sample.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPlaybackLocation(int playbackLocation) {
|
||||||
|
if (this.playbackLocation == playbackLocation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// View management.
|
||||||
|
if (playbackLocation == PLAYBACK_LOCAL) {
|
||||||
|
exoPlayerView.setVisibility(View.VISIBLE);
|
||||||
|
castControlView.hide();
|
||||||
|
} else {
|
||||||
|
exoPlayerView.setVisibility(View.GONE);
|
||||||
|
castControlView.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
long playbackPositionMs = 0;
|
||||||
|
boolean playWhenReady = true;
|
||||||
|
if (exoPlayer != null) {
|
||||||
|
playbackPositionMs = exoPlayer.getCurrentPosition();
|
||||||
|
playWhenReady = exoPlayer.getPlayWhenReady();
|
||||||
|
} else if (this.playbackLocation == PLAYBACK_REMOTE) {
|
||||||
|
playbackPositionMs = castPlayer.getCurrentPosition();
|
||||||
|
playWhenReady = castPlayer.getPlayWhenReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.playbackLocation = playbackLocation;
|
||||||
|
if (currentSample != null) {
|
||||||
|
setCurrentSample(currentSample, playbackPositionMs, playWhenReady);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
41
demos/cast/src/main/res/layout/main_activity.xml
Normal file
41
demos/cast/src/main/res/layout/main_activity.xml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:keepScreenOn="true">
|
||||||
|
<com.google.android.exoplayer2.ui.SimpleExoPlayerView android:id="@+id/player_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:repeat_toggle_modes="all|one"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="12" />
|
||||||
|
<ListView
|
||||||
|
android:id="@+id/sample_list"
|
||||||
|
android:choiceMode="singleChoice"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="12" />
|
||||||
|
<com.google.android.exoplayer2.ui.PlaybackControlView
|
||||||
|
android:id="@+id/cast_control_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:show_timeout="-1"
|
||||||
|
android:layout_weight="2"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
</LinearLayout>
|
25
demos/cast/src/main/res/menu/menu.xml
Normal file
25
demos/cast/src/main/res/menu/menu.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/media_route_menu_item"
|
||||||
|
android:title="@string/media_route_menu_title"
|
||||||
|
app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
</menu>
|
BIN
demos/cast/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
demos/cast/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
BIN
demos/cast/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
demos/cast/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
demos/cast/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
demos/cast/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
BIN
demos/cast/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
demos/cast/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
demos/cast/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
demos/cast/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
25
demos/cast/src/main/res/values/strings.xml
Normal file
25
demos/cast/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="application_name">ExoCast Demo</string>
|
||||||
|
|
||||||
|
<string name="media_route_menu_title">ExoCast</string>
|
||||||
|
|
||||||
|
<string name="error_unsupported_drm">DRM scheme not supported by this device.</string>
|
||||||
|
|
||||||
|
</resources>
|
22
demos/cast/src/main/res/values/styles.xml
Normal file
22
demos/cast/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?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.
|
||||||
|
-->
|
||||||
|
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<style name="PlayerTheme" parent="android:Theme.Holo">
|
||||||
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
33
extensions/cast/README.md
Normal file
33
extensions/cast/README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# ExoPlayer Cast extension #
|
||||||
|
|
||||||
|
## Description ##
|
||||||
|
|
||||||
|
The cast extension is a [Player][] implementation that controls playback on a
|
||||||
|
Cast receiver app.
|
||||||
|
|
||||||
|
[Player]: https://google.github.io/ExoPlayer/doc/reference/index.html?com/google/android/exoplayer2/Player.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-cast: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
|
||||||
|
|
||||||
|
## Using the extension ##
|
||||||
|
|
||||||
|
Create a `CastPlayer` and use it to integrate Cast into your app using
|
||||||
|
ExoPlayer's common Player interface. You can try the Cast Extension to see how a
|
||||||
|
[PlaybackControlView][] can be used to control playback in a remote receiver app.
|
||||||
|
|
||||||
|
[PlaybackControlView]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/ui/PlaybackControlView.html
|
45
extensions/cast/build.gradle
Normal file
45
extensions/cast/build.gradle
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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 14
|
||||||
|
targetSdkVersion project.ext.targetSdkVersion
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile 'com.android.support:appcompat-v7:' + supportLibraryVersion
|
||||||
|
compile 'com.android.support:mediarouter-v7:' + supportLibraryVersion
|
||||||
|
compile 'com.google.android.gms:play-services-cast-framework:' + playServicesLibraryVersion
|
||||||
|
compile project(modulePrefix + 'library-core')
|
||||||
|
compile project(modulePrefix + 'library-ui')
|
||||||
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
javadocTitle = 'Cast extension'
|
||||||
|
}
|
||||||
|
apply from: '../../javadoc_library.gradle'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
releaseArtifact = 'extension-cast'
|
||||||
|
releaseDescription = 'Cast extension for ExoPlayer.'
|
||||||
|
}
|
||||||
|
apply from: '../../publish.gradle'
|
16
extensions/cast/src/main/AndroidManifest.xml
Normal file
16
extensions/cast/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?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.cast"/>
|
@ -0,0 +1,649 @@
|
|||||||
|
/*
|
||||||
|
* 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.cast;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import com.google.android.gms.cast.CastStatusCodes;
|
||||||
|
import com.google.android.gms.cast.MediaInfo;
|
||||||
|
import com.google.android.gms.cast.MediaMetadata;
|
||||||
|
import com.google.android.gms.cast.MediaStatus;
|
||||||
|
import com.google.android.gms.cast.MediaTrack;
|
||||||
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
|
import com.google.android.gms.cast.framework.CastSession;
|
||||||
|
import com.google.android.gms.cast.framework.SessionManager;
|
||||||
|
import com.google.android.gms.cast.framework.SessionManagerListener;
|
||||||
|
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
|
||||||
|
import com.google.android.gms.cast.framework.media.RemoteMediaClient.MediaChannelResult;
|
||||||
|
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||||
|
import com.google.android.gms.common.api.ResultCallback;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Player} implementation that communicates with a Cast receiver app.
|
||||||
|
*
|
||||||
|
* <p>Calls to the methods in this class depend on the availability of an underlying cast session.
|
||||||
|
* If no session is available, method calls have no effect. To keep track of the underyling session,
|
||||||
|
* {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
|
||||||
|
* implemented and attached to the player.
|
||||||
|
*
|
||||||
|
* <p>Methods should be called on the application's main thread.
|
||||||
|
*
|
||||||
|
* <p>Known issues:
|
||||||
|
* <ul>
|
||||||
|
* <li>Part of the Cast API is not exposed through this interface. For instance, volume settings
|
||||||
|
* and track selection.</li>
|
||||||
|
* <li> Repeat mode is not working. See [internal: b/64137174].</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public final class CastPlayer implements Player {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener of changes in the cast session availability.
|
||||||
|
*/
|
||||||
|
public interface SessionAvailabilityListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a cast session becomes available to the player.
|
||||||
|
*/
|
||||||
|
void onCastSessionAvailable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the cast session becomes unavailable.
|
||||||
|
*/
|
||||||
|
void onCastSessionUnavailable();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String TAG = "CastPlayer";
|
||||||
|
|
||||||
|
private static final int RENDERER_COUNT = 3;
|
||||||
|
private static final int RENDERER_INDEX_VIDEO = 0;
|
||||||
|
private static final int RENDERER_INDEX_AUDIO = 1;
|
||||||
|
private static final int RENDERER_INDEX_TEXT = 2;
|
||||||
|
private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
|
||||||
|
private static final TrackGroupArray EMPTY_TRACK_GROUP_ARRAY = new TrackGroupArray();
|
||||||
|
private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
|
||||||
|
new TrackSelectionArray(null, null, null);
|
||||||
|
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
|
||||||
|
|
||||||
|
private final CastContext castContext;
|
||||||
|
private final Timeline.Window window;
|
||||||
|
|
||||||
|
// Result callbacks.
|
||||||
|
private final StatusListener statusListener;
|
||||||
|
private final RepeatModeResultCallback repeatModeResultCallback;
|
||||||
|
private final SeekResultCallback seekResultCallback;
|
||||||
|
|
||||||
|
// Listeners.
|
||||||
|
private final CopyOnWriteArraySet<EventListener> listeners;
|
||||||
|
private SessionAvailabilityListener sessionAvailabilityListener;
|
||||||
|
|
||||||
|
// Internal state.
|
||||||
|
private RemoteMediaClient remoteMediaClient;
|
||||||
|
private Timeline currentTimeline;
|
||||||
|
private TrackGroupArray currentTrackGroups;
|
||||||
|
private TrackSelectionArray currentTrackSelection;
|
||||||
|
private long lastReportedPositionMs;
|
||||||
|
private long pendingSeekPositionMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param castContext The context from which the cast session is obtained.
|
||||||
|
*/
|
||||||
|
public CastPlayer(CastContext castContext) {
|
||||||
|
this.castContext = castContext;
|
||||||
|
window = new Timeline.Window();
|
||||||
|
statusListener = new StatusListener();
|
||||||
|
repeatModeResultCallback = new RepeatModeResultCallback();
|
||||||
|
seekResultCallback = new SeekResultCallback();
|
||||||
|
listeners = new CopyOnWriteArraySet<>();
|
||||||
|
SessionManager sessionManager = castContext.getSessionManager();
|
||||||
|
sessionManager.addSessionManagerListener(statusListener, CastSession.class);
|
||||||
|
CastSession session = sessionManager.getCurrentCastSession();
|
||||||
|
remoteMediaClient = session != null ? session.getRemoteMediaClient() : null;
|
||||||
|
pendingSeekPositionMs = C.TIME_UNSET;
|
||||||
|
updateInternalState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads media into the receiver app.
|
||||||
|
*
|
||||||
|
* @param title The title of the media sample.
|
||||||
|
* @param url The url from which the media is obtained.
|
||||||
|
* @param contentMimeType The mime type of the content to play.
|
||||||
|
* @param positionMs The position at which the playback should start in milliseconds.
|
||||||
|
* @param playWhenReady Whether the player should start playback as soon as it is ready to do so.
|
||||||
|
*/
|
||||||
|
public void load(String title, String url, String contentMimeType, long positionMs,
|
||||||
|
boolean playWhenReady) {
|
||||||
|
lastReportedPositionMs = 0;
|
||||||
|
if (remoteMediaClient != null) {
|
||||||
|
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
|
||||||
|
movieMetadata.putString(MediaMetadata.KEY_TITLE, title);
|
||||||
|
MediaInfo mediaInfo = new MediaInfo.Builder(url).setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||||
|
.setContentType(contentMimeType).setMetadata(movieMetadata).build();
|
||||||
|
remoteMediaClient.load(mediaInfo, playWhenReady, positionMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a cast session is available for playback.
|
||||||
|
*/
|
||||||
|
public boolean isCastSessionAvailable() {
|
||||||
|
return remoteMediaClient != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a listener for updates on the cast session availability.
|
||||||
|
*
|
||||||
|
* @param listener The {@link SessionAvailabilityListener}.
|
||||||
|
*/
|
||||||
|
public void setSessionAvailabilityListener(SessionAvailabilityListener listener) {
|
||||||
|
sessionAvailabilityListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addListener(EventListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeListener(EventListener listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPlaybackState() {
|
||||||
|
if (remoteMediaClient == null) {
|
||||||
|
return STATE_IDLE;
|
||||||
|
}
|
||||||
|
int receiverAppStatus = remoteMediaClient.getPlayerState();
|
||||||
|
switch (receiverAppStatus) {
|
||||||
|
case MediaStatus.PLAYER_STATE_BUFFERING:
|
||||||
|
return STATE_BUFFERING;
|
||||||
|
case MediaStatus.PLAYER_STATE_PLAYING:
|
||||||
|
case MediaStatus.PLAYER_STATE_PAUSED:
|
||||||
|
return STATE_READY;
|
||||||
|
case MediaStatus.PLAYER_STATE_IDLE:
|
||||||
|
case MediaStatus.PLAYER_STATE_UNKNOWN:
|
||||||
|
default:
|
||||||
|
return STATE_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlayWhenReady(boolean playWhenReady) {
|
||||||
|
if (remoteMediaClient == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (playWhenReady) {
|
||||||
|
remoteMediaClient.play();
|
||||||
|
} else {
|
||||||
|
remoteMediaClient.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getPlayWhenReady() {
|
||||||
|
return remoteMediaClient != null && !remoteMediaClient.isPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekToDefaultPosition() {
|
||||||
|
seekTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekToDefaultPosition(int windowIndex) {
|
||||||
|
seekTo(windowIndex, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(long positionMs) {
|
||||||
|
seekTo(0, positionMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(int windowIndex, long positionMs) {
|
||||||
|
if (remoteMediaClient != null) {
|
||||||
|
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
|
||||||
|
pendingSeekPositionMs = positionMs;
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onPositionDiscontinuity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
||||||
|
// Unsupported by the RemoteMediaClient API. Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
|
return PlaybackParameters.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (remoteMediaClient != null) {
|
||||||
|
remoteMediaClient.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
castContext.getSessionManager().removeSessionManagerListener(statusListener, CastSession.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRendererCount() {
|
||||||
|
// We assume there are three renderers: video, audio, and text.
|
||||||
|
return RENDERER_COUNT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRendererType(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case RENDERER_INDEX_VIDEO:
|
||||||
|
return C.TRACK_TYPE_VIDEO;
|
||||||
|
case RENDERER_INDEX_AUDIO:
|
||||||
|
return C.TRACK_TYPE_AUDIO;
|
||||||
|
case RENDERER_INDEX_TEXT:
|
||||||
|
return C.TRACK_TYPE_TEXT;
|
||||||
|
default:
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||||
|
if (remoteMediaClient != null) {
|
||||||
|
int castRepeatMode;
|
||||||
|
switch (repeatMode) {
|
||||||
|
case REPEAT_MODE_ONE:
|
||||||
|
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_SINGLE;
|
||||||
|
break;
|
||||||
|
case REPEAT_MODE_ALL:
|
||||||
|
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_ALL;
|
||||||
|
break;
|
||||||
|
case REPEAT_MODE_OFF:
|
||||||
|
castRepeatMode = MediaStatus.REPEAT_MODE_REPEAT_OFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
remoteMediaClient.queueSetRepeatMode(castRepeatMode, null)
|
||||||
|
.setResultCallback(repeatModeResultCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@RepeatMode public int getRepeatMode() {
|
||||||
|
if (remoteMediaClient == null) {
|
||||||
|
return REPEAT_MODE_OFF;
|
||||||
|
}
|
||||||
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
|
if (mediaStatus == null) {
|
||||||
|
// No media session active, yet.
|
||||||
|
return REPEAT_MODE_OFF;
|
||||||
|
}
|
||||||
|
int castRepeatMode = mediaStatus.getQueueRepeatMode();
|
||||||
|
switch (castRepeatMode) {
|
||||||
|
case MediaStatus.REPEAT_MODE_REPEAT_SINGLE:
|
||||||
|
return REPEAT_MODE_ONE;
|
||||||
|
case MediaStatus.REPEAT_MODE_REPEAT_ALL:
|
||||||
|
case MediaStatus.REPEAT_MODE_REPEAT_ALL_AND_SHUFFLE:
|
||||||
|
return REPEAT_MODE_ALL;
|
||||||
|
case MediaStatus.REPEAT_MODE_REPEAT_OFF:
|
||||||
|
return REPEAT_MODE_OFF;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackSelectionArray getCurrentTrackSelections() {
|
||||||
|
return currentTrackSelection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackGroupArray getCurrentTrackGroups() {
|
||||||
|
return currentTrackGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline getCurrentTimeline() {
|
||||||
|
return currentTimeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable public Object getCurrentManifest() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentPeriodIndex() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentWindowIndex() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDuration() {
|
||||||
|
return currentTimeline.isEmpty() ? C.TIME_UNSET
|
||||||
|
: currentTimeline.getWindow(0, window).getDurationMs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return remoteMediaClient == null ? lastReportedPositionMs
|
||||||
|
: pendingSeekPositionMs != C.TIME_UNSET ? pendingSeekPositionMs
|
||||||
|
: remoteMediaClient.getApproximateStreamPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBufferedPosition() {
|
||||||
|
return getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBufferedPercentage() {
|
||||||
|
long position = getBufferedPosition();
|
||||||
|
long duration = getDuration();
|
||||||
|
return position == C.TIME_UNSET || duration == C.TIME_UNSET ? 0
|
||||||
|
: duration == 0 ? 100
|
||||||
|
: Util.constrainValue((int) ((position * 100) / duration), 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCurrentWindowDynamic() {
|
||||||
|
return !currentTimeline.isEmpty()
|
||||||
|
&& currentTimeline.getWindow(getCurrentWindowIndex(), window).isDynamic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCurrentWindowSeekable() {
|
||||||
|
return !currentTimeline.isEmpty()
|
||||||
|
&& currentTimeline.getWindow(getCurrentWindowIndex(), window).isSeekable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlayingAd() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentAdGroupIndex() {
|
||||||
|
return C.INDEX_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentAdIndexInAdGroup() {
|
||||||
|
return C.INDEX_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoading() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getContentPosition() {
|
||||||
|
return getCurrentPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
|
private void setRemoteMediaClient(@Nullable RemoteMediaClient remoteMediaClient) {
|
||||||
|
if (this.remoteMediaClient == remoteMediaClient) {
|
||||||
|
// Do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.remoteMediaClient != null) {
|
||||||
|
this.remoteMediaClient.removeListener(statusListener);
|
||||||
|
this.remoteMediaClient.removeProgressListener(statusListener);
|
||||||
|
}
|
||||||
|
this.remoteMediaClient = remoteMediaClient;
|
||||||
|
if (remoteMediaClient != null) {
|
||||||
|
if (sessionAvailabilityListener != null) {
|
||||||
|
sessionAvailabilityListener.onCastSessionAvailable();
|
||||||
|
}
|
||||||
|
remoteMediaClient.addListener(statusListener);
|
||||||
|
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
|
||||||
|
} else {
|
||||||
|
if (sessionAvailabilityListener != null) {
|
||||||
|
sessionAvailabilityListener.onCastSessionUnavailable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable MediaStatus getMediaStatus() {
|
||||||
|
return remoteMediaClient != null ? remoteMediaClient.getMediaStatus() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable MediaInfo getMediaInfo() {
|
||||||
|
return remoteMediaClient != null ? remoteMediaClient.getMediaInfo() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInternalState() {
|
||||||
|
currentTimeline = Timeline.EMPTY;
|
||||||
|
currentTrackGroups = EMPTY_TRACK_GROUP_ARRAY;
|
||||||
|
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
||||||
|
MediaInfo mediaInfo = getMediaInfo();
|
||||||
|
if (mediaInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long streamDurationMs = mediaInfo.getStreamDuration();
|
||||||
|
boolean isSeekable = streamDurationMs != MediaInfo.UNKNOWN_DURATION;
|
||||||
|
currentTimeline = new SinglePeriodTimeline(
|
||||||
|
isSeekable ? C.msToUs(streamDurationMs) : C.TIME_UNSET, isSeekable);
|
||||||
|
|
||||||
|
List<MediaTrack> tracks = mediaInfo.getMediaTracks();
|
||||||
|
if (tracks == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
|
long[] activeTrackIds = mediaStatus != null ? mediaStatus.getActiveTrackIds() : null;
|
||||||
|
if (activeTrackIds == null) {
|
||||||
|
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackGroup[] trackGroups = new TrackGroup[tracks.size()];
|
||||||
|
TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
|
||||||
|
for (int i = 0; i < tracks.size(); i++) {
|
||||||
|
MediaTrack mediaTrack = tracks.get(i);
|
||||||
|
trackGroups[i] = new TrackGroup(CastUtils.mediaTrackToFormat(mediaTrack));
|
||||||
|
|
||||||
|
long id = mediaTrack.getId();
|
||||||
|
int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
|
||||||
|
int rendererIndex = getRendererIndexForTrackType(trackType);
|
||||||
|
if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET
|
||||||
|
&& trackSelections[rendererIndex] == null) {
|
||||||
|
trackSelections[rendererIndex] = new FixedTrackSelection(trackGroups[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentTrackSelection = new TrackSelectionArray(trackSelections);
|
||||||
|
currentTrackGroups = new TrackGroupArray(trackGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTrackActive(long id, long[] activeTrackIds) {
|
||||||
|
for (long activeTrackId : activeTrackIds) {
|
||||||
|
if (activeTrackId == id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getRendererIndexForTrackType(int trackType) {
|
||||||
|
return trackType == C.TRACK_TYPE_VIDEO ? RENDERER_INDEX_VIDEO
|
||||||
|
: trackType == C.TRACK_TYPE_AUDIO ? RENDERER_INDEX_AUDIO
|
||||||
|
: trackType == C.TRACK_TYPE_TEXT ? RENDERER_INDEX_TEXT
|
||||||
|
: C.INDEX_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StatusListener implements RemoteMediaClient.Listener,
|
||||||
|
SessionManagerListener<CastSession>, RemoteMediaClient.ProgressListener {
|
||||||
|
|
||||||
|
// RemoteMediaClient.ProgressListener implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressUpdated(long progressMs, long unusedDurationMs) {
|
||||||
|
lastReportedPositionMs = progressMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteMediaClient.Listener implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusUpdated() {
|
||||||
|
boolean playWhenReady = getPlayWhenReady();
|
||||||
|
int playbackState = getPlaybackState();
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMetadataUpdated() {
|
||||||
|
updateInternalState();
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onTracksChanged(currentTrackGroups, currentTrackSelection);
|
||||||
|
listener.onTimelineChanged(currentTimeline, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onQueueStatusUpdated() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreloadStatusUpdated() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendingRemoteMediaRequest() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAdBreakStatusUpdated() {}
|
||||||
|
|
||||||
|
|
||||||
|
// SessionManagerListener implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionStarted(CastSession castSession, String s) {
|
||||||
|
setRemoteMediaClient(castSession.getRemoteMediaClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionResumed(CastSession castSession, boolean b) {
|
||||||
|
setRemoteMediaClient(castSession.getRemoteMediaClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionEnded(CastSession castSession, int i) {
|
||||||
|
setRemoteMediaClient(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionSuspended(CastSession castSession, int i) {
|
||||||
|
setRemoteMediaClient(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionResumeFailed(CastSession castSession, int statusCode) {
|
||||||
|
Log.e(TAG, "Session resume failed. Error code " + statusCode + ": "
|
||||||
|
+ CastUtils.getLogString(statusCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionStarting(CastSession castSession) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionStartFailed(CastSession castSession, int statusCode) {
|
||||||
|
Log.e(TAG, "Session start failed. Error code " + statusCode + ": "
|
||||||
|
+ CastUtils.getLogString(statusCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionEnding(CastSession castSession) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSessionResuming(CastSession castSession, String s) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result callbacks hooks.
|
||||||
|
|
||||||
|
private final class RepeatModeResultCallback implements ResultCallback<MediaChannelResult> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResult(MediaChannelResult result) {
|
||||||
|
int statusCode = result.getStatus().getStatusCode();
|
||||||
|
if (statusCode == CommonStatusCodes.SUCCESS) {
|
||||||
|
int repeatMode = getRepeatMode();
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onRepeatModeChanged(repeatMode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Set repeat mode failed. Error code " + statusCode + ": "
|
||||||
|
+ CastUtils.getLogString(statusCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResult(MediaChannelResult result) {
|
||||||
|
int statusCode = result.getStatus().getStatusCode();
|
||||||
|
if (statusCode == CommonStatusCodes.SUCCESS) {
|
||||||
|
pendingSeekPositionMs = C.TIME_UNSET;
|
||||||
|
} else if (statusCode == CastStatusCodes.REPLACED) {
|
||||||
|
// A seek was executed before this one completed. Do nothing.
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
|
||||||
|
+ CastUtils.getLogString(statusCode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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.cast;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.gms.cast.CastStatusCodes;
|
||||||
|
import com.google.android.gms.cast.MediaTrack;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for ExoPlayer/Cast integration.
|
||||||
|
*/
|
||||||
|
/* package */ final class CastUtils {
|
||||||
|
|
||||||
|
private static final Map<Integer, String> CAST_STATUS_CODE_TO_STRING;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a descriptive log string for the given {@code statusCode}, or "Unknown." if not one of
|
||||||
|
* {@link CastStatusCodes}.
|
||||||
|
*
|
||||||
|
* @param statusCode A Cast API status code.
|
||||||
|
* @return A descriptive log string for the given {@code statusCode}, or "Unknown." if not one of
|
||||||
|
* {@link CastStatusCodes}.
|
||||||
|
*/
|
||||||
|
public static String getLogString(int statusCode) {
|
||||||
|
String description = CAST_STATUS_CODE_TO_STRING.get(statusCode);
|
||||||
|
return description != null ? description : "Unknown.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link Format} instance containing all information contained in the given
|
||||||
|
* {@link MediaTrack} object.
|
||||||
|
*
|
||||||
|
* @param mediaTrack The {@link MediaTrack}.
|
||||||
|
* @return The equivalent {@link Format}.
|
||||||
|
*/
|
||||||
|
public static Format mediaTrackToFormat(MediaTrack mediaTrack) {
|
||||||
|
return Format.createContainerFormat(mediaTrack.getContentId(), mediaTrack.getContentType(),
|
||||||
|
null, null, Format.NO_VALUE, 0, mediaTrack.getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
HashMap<Integer, String> statusCodeToString = new HashMap<>();
|
||||||
|
statusCodeToString.put(CastStatusCodes.APPLICATION_NOT_FOUND,
|
||||||
|
"A requested application could not be found.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.APPLICATION_NOT_RUNNING,
|
||||||
|
"A requested application is not currently running.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.AUTHENTICATION_FAILED, "Authentication failure.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.CANCELED, "An in-progress request has been "
|
||||||
|
+ "canceled, most likely because another action has preempted it.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.ERROR_SERVICE_CREATION_FAILED,
|
||||||
|
"The Cast Remote Display service could not be created.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.ERROR_SERVICE_DISCONNECTED,
|
||||||
|
"The Cast Remote Display service was disconnected.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.FAILED, "The in-progress request failed.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.INTERNAL_ERROR, "An internal error has occurred.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.INTERRUPTED,
|
||||||
|
"A blocking call was interrupted while waiting and did not run to completion.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.INVALID_REQUEST, "An invalid request was made.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.MESSAGE_SEND_BUFFER_TOO_FULL, "A message could "
|
||||||
|
+ "not be sent because there is not enough room in the send buffer at this time.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.MESSAGE_TOO_LARGE,
|
||||||
|
"A message could not be sent because it is too large.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.NETWORK_ERROR, "Network I/O error.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.NOT_ALLOWED,
|
||||||
|
"The request was disallowed and could not be completed.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.REPLACED,
|
||||||
|
"The request's progress is no longer being tracked because another request of the same type"
|
||||||
|
+ " has been made before the first request completed.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.SUCCESS, "Success.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.TIMEOUT, "An operation has timed out.");
|
||||||
|
statusCodeToString.put(CastStatusCodes.UNKNOWN_ERROR,
|
||||||
|
"An unknown, unexpected error has occurred.");
|
||||||
|
CAST_STATUS_CODE_TO_STRING = Collections.unmodifiableMap(statusCodeToString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CastUtils() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.cast;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import com.google.android.gms.cast.CastMediaControlIntent;
|
||||||
|
import com.google.android.gms.cast.framework.CastOptions;
|
||||||
|
import com.google.android.gms.cast.framework.OptionsProvider;
|
||||||
|
import com.google.android.gms.cast.framework.SessionProvider;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience {@link OptionsProvider} to target the default cast receiver app.
|
||||||
|
*/
|
||||||
|
public final class DefaultCastOptionsProvider implements OptionsProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CastOptions getCastOptions(Context context) {
|
||||||
|
return new CastOptions.Builder()
|
||||||
|
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
|
||||||
|
.setStopReceiverApplicationWhenEndingSession(true).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SessionProvider> getAdditionalSessionProviders(Context context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,7 +22,7 @@ dependencies {
|
|||||||
// |-- com.android.support:support-v4:25.2.0
|
// |-- com.android.support:support-v4:25.2.0
|
||||||
compile 'com.android.support:support-v4:' + supportLibraryVersion
|
compile 'com.android.support:support-v4:' + supportLibraryVersion
|
||||||
compile 'com.google.ads.interactivemedia.v3:interactivemedia:3.7.4'
|
compile 'com.google.ads.interactivemedia.v3:interactivemedia:3.7.4'
|
||||||
compile 'com.google.android.gms:play-services-ads:11.0.2'
|
compile 'com.google.android.gms:play-services-ads:' + playServicesLibraryVersion
|
||||||
androidTestCompile project(modulePrefix + 'library')
|
androidTestCompile project(modulePrefix + 'library')
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
|
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
|
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
|
||||||
|
@ -13,5 +13,4 @@
|
|||||||
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.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<manifest package="com.google.android.exoplayer2"/>
|
<manifest package="com.google.android.exoplayer2"/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user