Remove cast extension from 2.6.0 release

This commit is contained in:
Oliver Woodman 2017-11-17 19:26:55 +00:00
parent 444358521c
commit 47a9609d3f
23 changed files with 0 additions and 1812 deletions

View File

@ -28,7 +28,6 @@ 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'
@ -47,7 +46,6 @@ 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')

View File

@ -1,4 +0,0 @@
# Cast demo application #
This folder contains a demo application that showcases ExoPlayer integration
with Google Cast.

View File

@ -1,51 +0,0 @@
// 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')
}

View File

@ -1,42 +0,0 @@
<?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="2600"
android:versionName="2.6.0">
<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"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,92 +0,0 @@
/*
* 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 DemoUtil {
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 mimeType;
/**
* @param uri See {@link #uri}.
* @param name See {@link #name}.
* @param mimeType See {@link #mimeType}.
*/
public Sample(String uri, String name, String mimeType) {
this.uri = uri;
this.name = name;
this.mimeType = mimeType;
}
@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 DemoUtil() {}
}

View File

@ -1,122 +0,0 @@
/*
* 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.annotation.NonNull;
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.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 = findViewById(R.id.player_view);
simpleExoPlayerView.requestFocus();
castControlView = findViewById(R.id.cast_control_view);
ListView sampleList = 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 onResume() {
super.onResume();
playerManager = new PlayerManager(simpleExoPlayerView, castControlView, this);
}
@Override
public void onPause() {
super.onPause();
playerManager.release();
playerManager = null;
}
// 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);
}
// User controls.
private final class SampleListAdapter extends ArrayAdapter<DemoUtil.Sample> {
public SampleListAdapter() {
super(getApplicationContext(), android.R.layout.simple_list_item_1, DemoUtil.SAMPLES);
}
@Override
@NonNull
public View getView(int position, View convertView, @NonNull 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) {
DemoUtil.Sample currentSample = DemoUtil.SAMPLES.get(position);
playerManager.setCurrentSample(currentSample, 0, true);
}
}
}
}

View File

@ -1,208 +0,0 @@
/*
* 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.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.cast.MediaQueueItem;
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 SimpleExoPlayer exoPlayer;
private final CastPlayer castPlayer;
private int playbackLocation;
private DemoUtil.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;
DefaultTrackSelector trackSelector = new DefaultTrackSelector(BANDWIDTH_METER);
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, null);
exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
exoPlayerView.setPlayer(exoPlayer);
castPlayer = new CastPlayer(CastContext.getSharedInstance(context));
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 DemoUtil} 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(DemoUtil.Sample currentSample, long positionMs,
boolean playWhenReady) {
this.currentSample = currentSample;
if (playbackLocation == PLAYBACK_REMOTE) {
castPlayer.loadItem(buildMediaQueueItem(currentSample), positionMs);
castPlayer.setPlayWhenReady(playWhenReady);
} else /* playbackLocation == PLAYBACK_LOCAL */ {
exoPlayer.prepare(buildMediaSource(currentSample), true, true);
exoPlayer.setPlayWhenReady(playWhenReady);
exoPlayer.seekTo(positionMs);
}
}
/**
* 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 MediaQueueItem buildMediaQueueItem(DemoUtil.Sample sample) {
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
movieMetadata.putString(MediaMetadata.KEY_TITLE, sample.name);
MediaInfo mediaInfo = new MediaInfo.Builder(sample.uri)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED).setContentType(sample.mimeType)
.setMetadata(movieMetadata).build();
return new MediaQueueItem.Builder(mediaInfo).build();
}
private static MediaSource buildMediaSource(DemoUtil.Sample sample) {
Uri uri = Uri.parse(sample.uri);
switch (sample.mimeType) {
case DemoUtil.MIME_TYPE_SS:
return new SsMediaSource(uri, DATA_SOURCE_FACTORY,
new DefaultSsChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
case DemoUtil.MIME_TYPE_DASH:
return new DashMediaSource(uri, DATA_SOURCE_FACTORY,
new DefaultDashChunkSource.Factory(DATA_SOURCE_FACTORY), null, null);
case DemoUtil.MIME_TYPE_HLS:
return new HlsMediaSource(uri, DATA_SOURCE_FACTORY, null, null);
case DemoUtil.MIME_TYPE_VIDEO_MP4:
return new ExtractorMediaSource(uri, DATA_SOURCE_FACTORY, new DefaultExtractorsFactory(),
null, null);
default: {
throw new IllegalStateException("Unsupported type: " + sample.mimeType);
}
}
}
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;
boolean playWhenReady;
if (this.playbackLocation == PLAYBACK_LOCAL) {
playbackPositionMs = exoPlayer.getCurrentPosition();
playWhenReady = exoPlayer.getPlayWhenReady();
exoPlayer.stop();
} else /* this.playbackLocation == PLAYBACK_REMOTE */ {
playbackPositionMs = castPlayer.getCurrentPosition();
playWhenReady = castPlayer.getPlayWhenReady();
castPlayer.stop();
}
this.playbackLocation = playbackLocation;
if (currentSample != null) {
setCurrentSample(currentSample, playbackPositionMs, playWhenReady);
}
}
}

View File

@ -1,42 +0,0 @@
<?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"
android:layout_weight="2"
android:visibility="gone"
app:repeat_toggle_modes="all|one"
app:show_timeout="-1"/>
</LinearLayout>

View File

@ -1,25 +0,0 @@
<?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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,25 +0,0 @@
<?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">Exo Cast Demo</string>
<string name="media_route_menu_title">Cast</string>
<string name="error_unsupported_drm">DRM scheme not supported by this device.</string>
</resources>

View File

@ -1,33 +0,0 @@
# 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

View File

@ -1,45 +0,0 @@
// 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'

View File

@ -1,16 +0,0 @@
<?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"/>

View File

@ -1,853 +0,0 @@
/*
* 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.NonNull;
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.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.Assertions;
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.MediaQueueItem;
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.PendingResult;
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>The behavior of this class depends on the underlying Cast session, which is obtained from the
* Cast context passed to {@link #CastPlayer}. To keep track of the session,
* {@link #isCastSessionAvailable()} can be queried and {@link SessionAvailabilityListener} can be
* implemented and attached to the player.</p>
*
* <p> If no session is available, the player state will remain unchanged and calls to methods that
* alter it will be ignored. Querying the player state is possible even when no session is
* available, in which case, the last observed receiver app state is reported.</p>
*
* <p>Methods should be called on the application's main thread.</p>
*/
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;
private final Timeline.Period period;
private RemoteMediaClient remoteMediaClient;
// Result callbacks.
private final StatusListener statusListener;
private final SeekResultCallback seekResultCallback;
// Listeners.
private final CopyOnWriteArraySet<EventListener> listeners;
private SessionAvailabilityListener sessionAvailabilityListener;
// Internal state.
private CastTimeline currentTimeline;
private TrackGroupArray currentTrackGroups;
private TrackSelectionArray currentTrackSelection;
private int playbackState;
private int repeatMode;
private int currentWindowIndex;
private boolean playWhenReady;
private long lastReportedPositionMs;
private int pendingSeekCount;
private int pendingSeekWindowIndex;
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();
period = new Timeline.Period();
statusListener = new StatusListener();
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;
playbackState = STATE_IDLE;
repeatMode = REPEAT_MODE_OFF;
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
currentTrackGroups = EMPTY_TRACK_GROUP_ARRAY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
pendingSeekWindowIndex = C.INDEX_UNSET;
pendingSeekPositionMs = C.TIME_UNSET;
updateInternalState();
}
// Media Queue manipulation methods.
/**
* Loads a single item media queue. If no session is available, does nothing.
*
* @param item The item to load.
* @param positionMs The position at which the playback should start in milliseconds relative to
* the start of the item at {@code startIndex}.
* @return The Cast {@code PendingResult}, or null if no session is available.
*/
public PendingResult<MediaChannelResult> loadItem(MediaQueueItem item, long positionMs) {
return loadItems(new MediaQueueItem[] {item}, 0, positionMs, REPEAT_MODE_OFF);
}
/**
* Loads a media queue. If no session is available, does nothing.
*
* @param items The items to load.
* @param startIndex The index of the item at which playback should start.
* @param positionMs The position at which the playback should start in milliseconds relative to
* the start of the item at {@code startIndex}.
* @param repeatMode The repeat mode for the created media queue.
* @return The Cast {@code PendingResult}, or null if no session is available.
*/
public PendingResult<MediaChannelResult> loadItems(MediaQueueItem[] items, int startIndex,
long positionMs, @RepeatMode int repeatMode) {
if (remoteMediaClient != null) {
return remoteMediaClient.queueLoad(items, startIndex, getCastRepeatMode(repeatMode),
positionMs, null);
}
return null;
}
/**
* Appends a sequence of items to the media queue. If no media queue exists, does nothing.
*
* @param items The items to append.
* @return The Cast {@code PendingResult}, or null if no media queue exists.
*/
public PendingResult<MediaChannelResult> addItems(MediaQueueItem... items) {
return addItems(MediaQueueItem.INVALID_ITEM_ID, items);
}
/**
* Inserts a sequence of items into the media queue. If no media queue or period with id
* {@code periodId} exist, does nothing.
*
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
* that will follow immediately after the inserted items.
* @param items The items to insert.
* @return The Cast {@code PendingResult}, or null if no media queue or no period with id
* {@code periodId} exist.
*/
public PendingResult<MediaChannelResult> addItems(int periodId, MediaQueueItem... items) {
if (getMediaStatus() != null && (periodId == MediaQueueItem.INVALID_ITEM_ID
|| currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET)) {
return remoteMediaClient.queueInsertItems(items, periodId, null);
}
return null;
}
/**
* Removes an item from the media queue. If no media queue or period with id {@code periodId}
* exist, does nothing.
*
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
* to remove.
* @return The Cast {@code PendingResult}, or null if no media queue or no period with id
* {@code periodId} exist.
*/
public PendingResult<MediaChannelResult> removeItem(int periodId) {
if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
return remoteMediaClient.queueRemoveItem(periodId, null);
}
return null;
}
/**
* Moves an existing item within the media queue. If no media queue or period with id
* {@code periodId} exist, does nothing.
*
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
* to move.
* @param newIndex The target index of the item in the media queue. Must be in the range
* 0 &lt;= index &lt; {@link Timeline#getPeriodCount()}, as provided by
* {@link #getCurrentTimeline()}.
* @return The Cast {@code PendingResult}, or null if no media queue or no period with id
* {@code periodId} exist.
*/
public PendingResult<MediaChannelResult> moveItem(int periodId, int newIndex) {
Assertions.checkArgument(newIndex >= 0 && newIndex < currentTimeline.getPeriodCount());
if (getMediaStatus() != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET) {
return remoteMediaClient.queueMoveItemToNewIndex(periodId, newIndex, null);
}
return null;
}
/**
* Returns the item that corresponds to the period with the given id, or null if no media queue or
* period with id {@code periodId} exist.
*
* @param periodId The id of the period ({@see #getCurrentTimeline}) that corresponds to the item
* to get.
* @return The item that corresponds to the period with the given id, or null if no media queue or
* period with id {@code periodId} exist.
*/
public MediaQueueItem getItem(int periodId) {
MediaStatus mediaStatus = getMediaStatus();
return mediaStatus != null && currentTimeline.getIndexOfPeriod(periodId) != C.INDEX_UNSET
? mediaStatus.getItemById(periodId) : null;
}
// CastSession methods.
/**
* Returns whether a cast session is available.
*/
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() {
return playbackState;
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
if (remoteMediaClient == null) {
return;
}
if (playWhenReady) {
remoteMediaClient.play();
} else {
remoteMediaClient.pause();
}
}
@Override
public boolean getPlayWhenReady() {
return playWhenReady;
}
@Override
public void seekToDefaultPosition() {
seekTo(0);
}
@Override
public void seekToDefaultPosition(int windowIndex) {
seekTo(windowIndex, 0);
}
@Override
public void seekTo(long positionMs) {
seekTo(getCurrentWindowIndex(), positionMs);
}
@Override
public void seekTo(int windowIndex, long positionMs) {
MediaStatus mediaStatus = getMediaStatus();
if (mediaStatus != null) {
if (getCurrentWindowIndex() != windowIndex) {
remoteMediaClient.queueJumpToItem((int) currentTimeline.getPeriod(windowIndex, period).uid,
positionMs, null).setResultCallback(seekResultCallback);
} else {
remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
}
pendingSeekCount++;
pendingSeekWindowIndex = windowIndex;
pendingSeekPositionMs = positionMs;
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
}
} else if (pendingSeekCount == 0) {
for (EventListener listener : listeners) {
listener.onSeekProcessed();
}
}
}
@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) {
remoteMediaClient.queueSetRepeatMode(getCastRepeatMode(repeatMode), null);
}
}
@Override
@RepeatMode public int getRepeatMode() {
return repeatMode;
}
@Override
public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
// TODO: Support shuffle mode.
}
@Override
public boolean getShuffleModeEnabled() {
// TODO: Support shuffle mode.
return false;
}
@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 getCurrentWindowIndex();
}
@Override
public int getCurrentWindowIndex() {
return pendingSeekWindowIndex != C.INDEX_UNSET ? pendingSeekWindowIndex : currentWindowIndex;
}
@Override
public int getNextWindowIndex() {
return C.INDEX_UNSET;
}
@Override
public int getPreviousWindowIndex() {
return C.INDEX_UNSET;
}
@Override
public long getDuration() {
return currentTimeline.isEmpty() ? C.TIME_UNSET
: currentTimeline.getWindow(getCurrentWindowIndex(), window).getDurationMs();
}
@Override
public long getCurrentPosition() {
return pendingSeekPositionMs != C.TIME_UNSET ? pendingSeekPositionMs
: remoteMediaClient != null ? remoteMediaClient.getApproximateStreamPosition()
: lastReportedPositionMs;
}
@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.
public void updateInternalState() {
if (remoteMediaClient == null) {
// There is no session. We leave the state of the player as it is now.
return;
}
int playbackState = fetchPlaybackState(remoteMediaClient);
boolean playWhenReady = !remoteMediaClient.isPaused();
if (this.playbackState != playbackState
|| this.playWhenReady != playWhenReady) {
this.playbackState = playbackState;
this.playWhenReady = playWhenReady;
for (EventListener listener : listeners) {
listener.onPlayerStateChanged(this.playWhenReady, this.playbackState);
}
}
@RepeatMode int repeatMode = fetchRepeatMode(remoteMediaClient);
if (this.repeatMode != repeatMode) {
this.repeatMode = repeatMode;
for (EventListener listener : listeners) {
listener.onRepeatModeChanged(repeatMode);
}
}
int currentWindowIndex = fetchCurrentWindowIndex(getMediaStatus());
if (this.currentWindowIndex != currentWindowIndex && pendingSeekCount == 0) {
this.currentWindowIndex = currentWindowIndex;
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity(DISCONTINUITY_REASON_PERIOD_TRANSITION);
}
}
if (updateTracksAndSelections()) {
for (EventListener listener : listeners) {
listener.onTracksChanged(currentTrackGroups, currentTrackSelection);
}
}
maybeUpdateTimelineAndNotify();
}
private void maybeUpdateTimelineAndNotify() {
if (updateTimeline()) {
for (EventListener listener : listeners) {
listener.onTimelineChanged(currentTimeline, null);
}
}
}
/**
* Updates the current timeline and returns whether it has changed.
*/
private boolean updateTimeline() {
MediaStatus mediaStatus = getMediaStatus();
if (mediaStatus == null) {
boolean hasChanged = currentTimeline != CastTimeline.EMPTY_CAST_TIMELINE;
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
return hasChanged;
}
List<MediaQueueItem> items = mediaStatus.getQueueItems();
if (!currentTimeline.represents(items)) {
currentTimeline = !items.isEmpty() ? new CastTimeline(mediaStatus.getQueueItems())
: CastTimeline.EMPTY_CAST_TIMELINE;
return true;
}
return false;
}
/**
* Updates the internal tracks and selection and returns whether they have changed.
*/
private boolean updateTracksAndSelections() {
if (remoteMediaClient == null) {
// There is no session. We leave the state of the player as it is now.
return false;
}
MediaStatus mediaStatus = getMediaStatus();
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
boolean hasChanged = currentTrackGroups != EMPTY_TRACK_GROUP_ARRAY;
currentTrackGroups = EMPTY_TRACK_GROUP_ARRAY;
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
return hasChanged;
}
long[] activeTrackIds = mediaStatus.getActiveTrackIds();
if (activeTrackIds == null) {
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
}
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()];
TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
for (int i = 0; i < castMediaTracks.size(); i++) {
MediaTrack mediaTrack = castMediaTracks.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);
}
}
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
if (!newTrackGroups.equals(currentTrackGroups)
|| !newTrackSelections.equals(currentTrackSelection)) {
currentTrackSelection = new TrackSelectionArray(trackSelections);
currentTrackGroups = new TrackGroupArray(trackGroups);
return true;
}
return false;
}
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);
updateInternalState();
} else {
if (sessionAvailabilityListener != null) {
sessionAvailabilityListener.onCastSessionUnavailable();
}
}
}
private @Nullable MediaStatus getMediaStatus() {
return remoteMediaClient != null ? remoteMediaClient.getMediaStatus() : null;
}
/**
* Retrieves the playback state from {@code remoteMediaClient} and maps it into a {@link Player}
* state
*/
private static int fetchPlaybackState(RemoteMediaClient remoteMediaClient) {
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;
}
}
/**
* Retrieves the repeat mode from {@code remoteMediaClient} and maps it into a
* {@link Player.RepeatMode}.
*/
@RepeatMode
private static int fetchRepeatMode(RemoteMediaClient remoteMediaClient) {
MediaStatus mediaStatus = remoteMediaClient.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();
}
}
/**
* Retrieves the current item index from {@code mediaStatus} and maps it into a window index. If
* there is no media session, returns 0.
*/
private static int fetchCurrentWindowIndex(@Nullable MediaStatus mediaStatus) {
Integer currentItemId = mediaStatus != null
? mediaStatus.getIndexById(mediaStatus.getCurrentItemId()) : null;
return currentItemId != null ? currentItemId : 0;
}
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 static int getCastRepeatMode(@RepeatMode int repeatMode) {
switch (repeatMode) {
case REPEAT_MODE_ONE:
return MediaStatus.REPEAT_MODE_REPEAT_SINGLE;
case REPEAT_MODE_ALL:
return MediaStatus.REPEAT_MODE_REPEAT_ALL;
case REPEAT_MODE_OFF:
return MediaStatus.REPEAT_MODE_REPEAT_OFF;
default:
throw new IllegalArgumentException();
}
}
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() {
updateInternalState();
}
@Override
public void onMetadataUpdated() {}
@Override
public void onQueueStatusUpdated() {
maybeUpdateTimelineAndNotify();
}
@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 SeekResultCallback implements ResultCallback<MediaChannelResult> {
@Override
public void onResult(@NonNull MediaChannelResult result) {
int statusCode = result.getStatus().getStatusCode();
if (statusCode != CastStatusCodes.SUCCESS && statusCode != CastStatusCodes.REPLACED) {
Log.e(TAG, "Seek failed. Error code " + statusCode + ": "
+ CastUtils.getLogString(statusCode));
}
if (--pendingSeekCount == 0) {
pendingSeekWindowIndex = C.INDEX_UNSET;
pendingSeekPositionMs = C.TIME_UNSET;
for (EventListener listener : listeners) {
listener.onSeekProcessed();
}
}
}
}
}

View File

@ -1,114 +0,0 @@
/*
* 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.util.SparseIntArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import java.util.Collections;
import java.util.List;
/**
* A {@link Timeline} for Cast media queues.
*/
/* package */ final class CastTimeline extends Timeline {
public static final CastTimeline EMPTY_CAST_TIMELINE =
new CastTimeline(Collections.<MediaQueueItem>emptyList());
private final SparseIntArray idsToIndex;
private final int[] ids;
private final long[] durationsUs;
private final long[] defaultPositionsUs;
public CastTimeline(List<MediaQueueItem> items) {
int itemCount = items.size();
int index = 0;
idsToIndex = new SparseIntArray(itemCount);
ids = new int[itemCount];
durationsUs = new long[itemCount];
defaultPositionsUs = new long[itemCount];
for (MediaQueueItem item : items) {
int itemId = item.getItemId();
ids[index] = itemId;
idsToIndex.put(itemId, index);
durationsUs[index] = getStreamDurationUs(item.getMedia());
defaultPositionsUs[index] = (long) (item.getStartTime() * C.MICROS_PER_SECOND);
index++;
}
}
@Override
public int getWindowCount() {
return ids.length;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
long durationUs = durationsUs[windowIndex];
boolean isDynamic = durationUs == C.TIME_UNSET;
return window.set(ids[windowIndex], C.TIME_UNSET, C.TIME_UNSET, !isDynamic, isDynamic,
defaultPositionsUs[windowIndex], durationUs, windowIndex, windowIndex, 0);
}
@Override
public int getPeriodCount() {
return ids.length;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
int id = ids[periodIndex];
return period.set(id, id, periodIndex, durationsUs[periodIndex], 0);
}
@Override
public int getIndexOfPeriod(Object uid) {
return uid instanceof Integer ? idsToIndex.get((int) uid, C.INDEX_UNSET) : C.INDEX_UNSET;
}
/**
* Returns whether the timeline represents a given {@code MediaQueueItem} list.
*
* @param items The {@code MediaQueueItem} list.
* @return Whether the timeline represents {@code items}.
*/
/* package */ boolean represents(List<MediaQueueItem> items) {
if (ids.length != items.size()) {
return false;
}
int index = 0;
for (MediaQueueItem item : items) {
if (ids[index] != item.getItemId()
|| durationsUs[index] != getStreamDurationUs(item.getMedia())
|| defaultPositionsUs[index] != (long) (item.getStartTime() * C.MICROS_PER_SECOND)) {
return false;
}
index++;
}
return true;
}
private static long getStreamDurationUs(MediaInfo mediaInfo) {
long durationMs = mediaInfo != null ? mediaInfo.getStreamDuration()
: MediaInfo.UNKNOWN_DURATION;
return durationMs != MediaInfo.UNKNOWN_DURATION ? C.msToUs(durationMs) : C.TIME_UNSET;
}
}

View File

@ -1,94 +0,0 @@
/*
* 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() {}
}

View File

@ -1,42 +0,0 @@
/*
* 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;
}
}

View File

@ -19,11 +19,9 @@ if (gradle.ext.has('exoplayerModulePrefix')) {
} }
include modulePrefix + 'demo' include modulePrefix + 'demo'
include modulePrefix + 'demo-cast'
include modulePrefix + 'demo-ima' include modulePrefix + 'demo-ima'
include modulePrefix + 'playbacktests' include modulePrefix + 'playbacktests'
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main') project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
project(modulePrefix + 'demo-ima').projectDir = new File(rootDir, 'demos/ima') project(modulePrefix + 'demo-ima').projectDir = new File(rootDir, 'demos/ima')
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests') project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')