mirror of
https://github.com/androidx/media.git
synced 2025-05-09 00:20:45 +08:00
Remove cast extension from 2.6.0 release
This commit is contained in:
parent
444358521c
commit
47a9609d3f
@ -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')
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# Cast demo application #
|
|
||||||
|
|
||||||
This folder contains a demo application that showcases ExoPlayer integration
|
|
||||||
with Google Cast.
|
|
@ -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')
|
|
||||||
}
|
|
@ -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>
|
|
@ -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() {}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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>
|
|
@ -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 |
@ -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>
|
|
@ -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
|
|
@ -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'
|
|
@ -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"/>
|
|
@ -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 <= index < {@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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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() {}
|
|
||||||
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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')
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user