Commit 27ab5c83 by Oliver Woodman

Initial drop. 1.0.10.

parents
Showing with 4831 additions and 0 deletions
# How to contribute #
We'd love to hear your feedback. Please open new issues describing any bugs,
feature requests or suggestions that you have.
We are not actively looking to accept patches to this project at the current
time, however in some cases we may do so. For such cases, please see the
agreement below.
## Contributor License Agreement ##
Contributions to any Google project must be accompanied by a Contributor
License Agreement. This is not a copyright **assignment**, it simply gives
Google permission to use and redistribute your contributions as part of the
project.
* If you are an individual writing original source code and you're sure you
own the intellectual property, then you'll need to sign an [individual
CLA][].
* If you work for a company that wants to allow you to contribute your work,
then you'll need to sign a [corporate CLA][].
You generally only need to submit a CLA once, so if you've already submitted
one (even if it was for a different project), you probably don't need to do it
again.
[individual CLA]: https://developers.google.com/open-source/cla/individual
[corporate CLA]: https://developers.google.com/open-source/cla/corporate
This diff is collapsed. Click to expand it.
# ExoPlayer Readme #
## Description ##
ExoPlayer is an application level media player for Android. It provides an
alternative to Android’s MediaPlayer API for playing audio and video both
locally and over the internet. ExoPlayer supports features not currently
supported by Android’s MediaPlayer API (as of KitKat), including DASH and
SmoothStreaming adaptive playbacks, persistent caching and custom renderers.
Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and
can be updated through Play Store application updates.
## Developer guide ##
The [ExoPlayer developer guide][] provides a wealth of information to help you
get started.
[ExoPlayer developer guide]: http://developer.android.com/guide/topics/media/exoplayer.html
## Using Eclipse ##
The repository includes Eclipse projects for both the ExoPlayer library and its
accompanying demo application. To get started:
1. Install Eclipse and setup the [Android SDK][].
1. Open Eclipse and navigate to File->Import->General->Existing Projects into
Workspace.
1. Select the root directory of the repository.
1. Import the ExoPlayerDemo and ExoPlayerLib projects.
[Android SDK]: http://developer.android.com/sdk/index.html
## Using Gradle ##
ExoPlayer can also be built using Gradle. For a complete list of tasks, run:
./gradlew tasks
// Copyright (C) 2014 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.
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.10.+'
}
}
allprojects {
repositories {
mavenCentral()
}
}
// Copyright (C) 2014 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 plugin: 'android'
android {
compileSdkVersion 19
buildToolsVersion "19.1"
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile project(':library')
}
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry combineaccessrules="false" kind="src" path="/ExoPlayerLib"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="src" path="java"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ExoPlayerDemo</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1363908154650</id>
<name></name>
<type>22</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-false-false-BUILD</arguments>
</matcher>
</filter>
<filter>
<id>1363908154652</id>
<name></name>
<type>10</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-true-false-build</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.source=1.6
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 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.exoplayer.demo"
android:versionCode="1010"
android:versionName="1.0.10"
android:theme="@style/RootTheme">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="19"/>
<application
android:label="@string/application_name"
android:allowBackup="true">
<activity android:name="com.google.android.exoplayer.demo.SampleChooserActivity"
android:configChanges="keyboardHidden"
android:label="@string/application_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.google.android.exoplayer.demo.simple.SimplePlayerActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/application_name"
android:theme="@style/PlayerTheme"/>
<activity android:name="com.google.android.exoplayer.demo.full.FullPlayerActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/application_name"
android:theme="@style/PlayerTheme"/>
</application>
</manifest>
/*
* Copyright (C) 2014 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.exoplayer.demo;
import com.google.android.exoplayer.ExoPlayerLibraryInfo;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.UUID;
/**
* Utility methods for the demo application.
*/
public class DemoUtil {
public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
public static final String CONTENT_TYPE_EXTRA = "content_type";
public static final String CONTENT_ID_EXTRA = "content_id";
public static final int TYPE_DASH_VOD = 0;
public static final int TYPE_SS_VOD = 1;
public static final int TYPE_OTHER = 2;
public static final boolean EXPOSE_EXPERIMENTAL_FEATURES = false;
public static String getUserAgent(Context context) {
String versionName;
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
versionName = info.versionName;
} catch (NameNotFoundException e) {
versionName = "?";
}
return "ExoPlayerDemo/" + versionName + " (Linux;Android " + Build.VERSION.RELEASE +
") " + "ExoPlayerLib/" + ExoPlayerLibraryInfo.VERSION;
}
public static byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
throws MalformedURLException, IOException {
HttpURLConnection urlConnection = null;
try {
urlConnection = (HttpURLConnection) new URL(url).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(data != null);
urlConnection.setDoInput(true);
if (requestProperties != null) {
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
urlConnection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
}
}
if (data != null) {
OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream());
out.write(data);
out.close();
}
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
return convertInputStreamToByteArray(in);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
private static byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
byte[] bytes = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte data[] = new byte[1024];
int count;
while ((count = inputStream.read(data)) != -1) {
bos.write(data, 0, count);
}
bos.flush();
bos.close();
inputStream.close();
bytes = bos.toByteArray();
return bytes;
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo;
import com.google.android.exoplayer.demo.Samples.Sample;
import com.google.android.exoplayer.demo.full.FullPlayerActivity;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity;
import com.google.android.exoplayer.util.Util;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
/**
* An activity for selecting from a number of samples.
*/
public class SampleChooserActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_chooser_activity);
ListView sampleList = (ListView) findViewById(R.id.sample_list);
final SampleAdapter sampleAdapter = new SampleAdapter(this);
sampleAdapter.add(new Header("Simple player"));
sampleAdapter.addAll((Object[]) Samples.SIMPLE);
sampleAdapter.add(new Header("YouTube DASH"));
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4);
sampleAdapter.add(new Header("Widevine DASH GTS"));
sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS);
sampleAdapter.add(new Header("SmoothStreaming"));
sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING);
sampleAdapter.add(new Header("Misc"));
sampleAdapter.addAll((Object[]) Samples.MISC);
if (DemoUtil.EXPOSE_EXPERIMENTAL_FEATURES) {
sampleAdapter.add(new Header("YouTube WebM DASH (Experimental)"));
sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM);
}
sampleList.setAdapter(sampleAdapter);
sampleList.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Object item = sampleAdapter.getItem(position);
if (item instanceof Sample) {
onSampleSelected((Sample) item);
}
}
});
}
private void onSampleSelected(Sample sample) {
if (Util.SDK_INT < 18 && sample.isEncypted) {
Toast.makeText(getApplicationContext(), R.string.drm_not_supported, Toast.LENGTH_SHORT)
.show();
return;
}
Class<?> playerActivityClass = sample.fullPlayer ? FullPlayerActivity.class
: SimplePlayerActivity.class;
Intent mpdIntent = new Intent(this, playerActivityClass)
.setData(Uri.parse(sample.uri))
.putExtra(DemoUtil.CONTENT_ID_EXTRA, sample.contentId)
.putExtra(DemoUtil.CONTENT_TYPE_EXTRA, sample.type);
startActivity(mpdIntent);
}
private static class SampleAdapter extends ArrayAdapter<Object> {
public SampleAdapter(Context context) {
super(context, 0);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
int layoutId = getItemViewType(position) == 1 ? android.R.layout.simple_list_item_1
: R.layout.sample_chooser_inline_header;
view = LayoutInflater.from(getContext()).inflate(layoutId, null, false);
}
Object item = getItem(position);
String name = null;
if (item instanceof Sample) {
name = ((Sample) item).name;
} else if (item instanceof Header) {
name = ((Header) item).name;
}
((TextView) view).setText(name);
return view;
}
@Override
public int getItemViewType(int position) {
return (getItem(position) instanceof Sample) ? 1 : 0;
}
@Override
public int getViewTypeCount() {
return 2;
}
}
private static class Header {
public final String name;
public Header(String name) {
this.name = name;
}
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo;
/**
* Holds statically defined sample definitions.
*/
/* package */ class Samples {
public static class Sample {
public final String name;
public final String contentId;
public final String uri;
public final int type;
public final boolean isEncypted;
public final boolean fullPlayer;
public Sample(String name, String contentId, String uri, int type, boolean isEncrypted,
boolean fullPlayer) {
this.name = name;
this.contentId = contentId;
this.uri = uri;
this.type = type;
this.isEncypted = isEncrypted;
this.fullPlayer = fullPlayer;
}
}
public static final Sample[] SIMPLE = new Sample[] {
new Sample("Google Glass (DASH)", "bf5bb2419360daf1",
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
+ "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH_VOD, false,
false),
new Sample("Google Play (DASH)", "3aa39fa2cc27967f",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
+ "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH_VOD, false,
false),
new Sample("Super speed (SmoothStreaming)", "uid:ss:superspeed",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
DemoUtil.TYPE_SS_VOD, false, false),
new Sample("Dizzy (Misc)", "uid:misc:dizzy",
"http://html5demos.com/assets/dizzy.mp4", DemoUtil.TYPE_OTHER, false, false),
};
public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] {
new Sample("Google Glass", "bf5bb2419360daf1",
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&"
+ "ipbits=0&expire=19000000000&signature=255F6B3C07C753C88708C07EA31B7A1A10703C8D."
+ "2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0", DemoUtil.TYPE_DASH_VOD, false,
true),
new Sample("Google Play", "3aa39fa2cc27967f",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
+ "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=7181C59D0252B285D593E1B61D985D5B7C98DE2A."
+ "5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0", DemoUtil.TYPE_DASH_VOD, false,
true),
};
public static final Sample[] YOUTUBE_DASH_WEBM = new Sample[] {
new Sample("Google Glass", "bf5bb2419360daf1",
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?"
+ "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=A3EC7EE53ABE601B357F7CAB8B54AD0702CA85A7."
+ "446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0", DemoUtil.TYPE_DASH_VOD, false, true),
new Sample("Google Play", "3aa39fa2cc27967f",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?"
+ "as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0&"
+ "expire=19000000000&signature=B752B262C6D7262EC4E4EB67901E5D8F7058A81D."
+ "C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0", DemoUtil.TYPE_DASH_VOD, false, true),
};
public static final Sample[] SMOOTHSTREAMING = new Sample[] {
new Sample("Super speed", "uid:ss:superspeed",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism",
DemoUtil.TYPE_SS_VOD, false, true),
new Sample("Super speed (PlayReady)", "uid:ss:pr:superspeed",
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism",
DemoUtil.TYPE_SS_VOD, true, true),
};
public static final Sample[] WIDEVINE_GTS = new Sample[] {
new Sample("WV: HDCP not specified", "d286538032258a1c",
"http://www.youtube.com/api/manifest/dash/id/d286538032258a1c/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=41EA40A027A125A16292E0A5E3277A3B5FA9B938."
+ "0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true),
new Sample("WV: HDCP not required", "48fcc369939ac96c",
"http://www.youtube.com/api/manifest/dash/id/48fcc369939ac96c/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=315911BDCEED0FB0C763455BDCC97449DAAFA9E8."
+ "5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true),
new Sample("WV: HDCP required", "e06c39f1151da3df",
"http://www.youtube.com/api/manifest/dash/id/e06c39f1151da3df/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=A47A1E13E7243BD567601A75F79B34644D0DC592."
+ "B09589A34FA23527EFC1552907754BB8033870BD&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true),
new Sample("WV: Secure video path required", "0894c7c8719b28a0",
"http://www.youtube.com/api/manifest/dash/id/0894c7c8719b28a0/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=2847EE498970F6B45176766CD2802FEB4D4CB7B2."
+ "A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true),
new Sample("WV: HDCP + secure video path required", "efd045b1eb61888a",
"http://www.youtube.com/api/manifest/dash/id/efd045b1eb61888a/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=61611F115EEEC7BADE5536827343FFFE2D83D14F."
+ "2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true),
new Sample("WV: 30s license duration", "f9a34cab7b05881a",
"http://www.youtube.com/api/manifest/dash/id/f9a34cab7b05881a/source/youtube?"
+ "as=fmp4_audio_cenc,fmp4_sd_hd_cenc&sparams=ip,ipbits,expire,as&ip=0.0.0.0&ipbits=0"
+ "&expire=19000000000&signature=88DC53943385CED8CF9F37ADD9E9843E3BF621E6."
+ "22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0", DemoUtil.TYPE_DASH_VOD, true, true),
};
public static final Sample[] MISC = new Sample[] {
new Sample("Dizzy", "uid:misc:dizzy", "http://html5demos.com/assets/dizzy.mp4",
DemoUtil.TYPE_OTHER, false, true),
};
private Samples() {}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.full;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer.AudioTrackInitializationException;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.demo.full.player.DemoPlayer;
import com.google.android.exoplayer.util.VerboseLogUtil;
import android.media.MediaCodec.CryptoException;
import android.os.SystemClock;
import android.util.Log;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.Locale;
/**
* Logs player events using {@link Log}.
*/
public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener,
DemoPlayer.InternalErrorListener {
private static final String TAG = "EventLogger";
private static final NumberFormat TIME_FORMAT;
static {
TIME_FORMAT = NumberFormat.getInstance(Locale.US);
TIME_FORMAT.setMinimumFractionDigits(2);
TIME_FORMAT.setMaximumFractionDigits(2);
}
private long sessionStartTimeMs;
private long[] loadStartTimeMs;
public EventLogger() {
loadStartTimeMs = new long[DemoPlayer.RENDERER_COUNT];
}
public void startSession() {
sessionStartTimeMs = SystemClock.elapsedRealtime();
Log.d(TAG, "start [0]");
}
public void endSession() {
Log.d(TAG, "end [" + getSessionTimeString() + "]");
}
// DemoPlayer.Listener
@Override
public void onStateChanged(boolean playWhenReady, int state) {
Log.d(TAG, "state [" + getSessionTimeString() + ", " + playWhenReady + ", " +
getStateString(state) + "]");
}
@Override
public void onError(Exception e) {
Log.e(TAG, "playerFailed [" + getSessionTimeString() + "]", e);
}
@Override
public void onVideoSizeChanged(int width, int height) {
Log.d(TAG, "videoSizeChanged [" + width + ", " + height + "]");
}
// DemoPlayer.InfoListener
@Override
public void onBandwidthSample(int elapsedMs, long bytes, long bandwidthEstimate) {
Log.d(TAG, "bandwidth [" + getSessionTimeString() + ", " + bytes +
", " + getTimeString(elapsedMs) + ", " + bandwidthEstimate + "]");
}
@Override
public void onDroppedFrames(int count, long elapsed) {
Log.d(TAG, "droppedFrames [" + getSessionTimeString() + ", " + count + "]");
}
@Override
public void onLoadStarted(int sourceId, int formatId, int trigger, boolean isInitialization,
int mediaStartTimeMs, int mediaEndTimeMs, long totalBytes) {
loadStartTimeMs[sourceId] = SystemClock.elapsedRealtime();
if (VerboseLogUtil.isTagEnabled(TAG)) {
Log.v(TAG, "loadStart [" + getSessionTimeString() + ", " + sourceId
+ ", " + mediaStartTimeMs + ", " + mediaEndTimeMs + "]");
}
}
@Override
public void onLoadCompleted(int sourceId) {
if (VerboseLogUtil.isTagEnabled(TAG)) {
long downloadTime = SystemClock.elapsedRealtime() - loadStartTimeMs[sourceId];
Log.v(TAG, "loadEnd [" + getSessionTimeString() + ", " + sourceId + ", " +
downloadTime + "]");
}
}
@Override
public void onVideoFormatEnabled(int formatId, int trigger, int mediaTimeMs) {
Log.d(TAG, "videoFormat [" + getSessionTimeString() + ", " + formatId + ", " +
Integer.toString(trigger) + "]");
}
@Override
public void onAudioFormatEnabled(int formatId, int trigger, int mediaTimeMs) {
Log.d(TAG, "audioFormat [" + getSessionTimeString() + ", " + formatId + ", " +
Integer.toString(trigger) + "]");
}
// DemoPlayer.InternalErrorListener
@Override
public void onUpstreamError(int sourceId, IOException e) {
printInternalError("upstreamError", e);
}
@Override
public void onConsumptionError(int sourceId, IOException e) {
printInternalError("consumptionError", e);
}
@Override
public void onRendererInitializationError(Exception e) {
printInternalError("rendererInitError", e);
}
@Override
public void onDrmSessionManagerError(Exception e) {
printInternalError("drmSessionManagerError", e);
}
@Override
public void onDecoderInitializationError(DecoderInitializationException e) {
printInternalError("decoderInitializationError", e);
}
@Override
public void onAudioTrackInitializationError(AudioTrackInitializationException e) {
printInternalError("audioTrackInitializationError", e);
}
@Override
public void onCryptoError(CryptoException e) {
printInternalError("cryptoError", e);
}
private void printInternalError(String type, Exception e) {
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
}
private String getStateString(int state) {
switch (state) {
case ExoPlayer.STATE_BUFFERING:
return "B";
case ExoPlayer.STATE_ENDED:
return "E";
case ExoPlayer.STATE_IDLE:
return "I";
case ExoPlayer.STATE_PREPARING:
return "P";
case ExoPlayer.STATE_READY:
return "R";
default:
return "?";
}
}
private String getSessionTimeString() {
return getTimeString(SystemClock.elapsedRealtime() - sessionStartTimeMs);
}
private String getTimeString(long timeMs) {
return TIME_FORMAT.format((timeMs) / 1000f);
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.full;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.drm.MediaDrmCallback;
import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import android.annotation.TargetApi;
import android.media.MediaDrm.KeyRequest;
import android.media.MediaDrm.ProvisionRequest;
import android.text.TextUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Demo {@link StreamingDrmSessionManager} for smooth streaming test content.
*/
@TargetApi(18)
public class SmoothStreamingTestMediaDrmCallback implements MediaDrmCallback {
private static final String PLAYREADY_TEST_DEFAULT_URI =
"http://playready.directtaps.net/pr/svc/rightsmanager.asmx";
private static final Map<String, String> KEY_REQUEST_PROPERTIES;
static {
HashMap<String, String> keyRequestProperties = new HashMap<String, String>();
keyRequestProperties.put("Content-Type", "text/xml");
keyRequestProperties.put("SOAPAction",
"http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense");
KEY_REQUEST_PROPERTIES = keyRequestProperties;
}
@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return DemoUtil.executePost(url, null, null);
}
@Override
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception {
String url = request.getDefaultUrl();
if (TextUtils.isEmpty(url)) {
url = PLAYREADY_TEST_DEFAULT_URI;
}
return DemoUtil.executePost(url, request.getData(), KEY_REQUEST_PROPERTIES);
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.full;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.drm.MediaDrmCallback;
import android.annotation.TargetApi;
import android.media.MediaDrm.KeyRequest;
import android.media.MediaDrm.ProvisionRequest;
import android.text.TextUtils;
import org.apache.http.client.ClientProtocolException;
import java.io.IOException;
import java.util.UUID;
/**
* A {@link MediaDrmCallback} for Widevine test content.
*/
@TargetApi(18)
public class WidevineTestMediaDrmCallback implements MediaDrmCallback {
private static final String WIDEVINE_GTS_DEFAULT_BASE_URI =
"http://wv-staging-proxy.appspot.com/proxy?provider=YouTube&video_id=";
private final String defaultUri;
public WidevineTestMediaDrmCallback(String videoId) {
defaultUri = WIDEVINE_GTS_DEFAULT_BASE_URI + videoId;
}
@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request)
throws ClientProtocolException, IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
return DemoUtil.executePost(url, null, null);
}
@Override
public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws IOException {
String url = request.getDefaultUrl();
if (TextUtils.isEmpty(url)) {
url = defaultUri;
}
return DemoUtil.executePost(url, request.getData(), null);
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.full.player;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.MediaCodecTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.Format;
import android.widget.TextView;
/**
* A {@link TrackRenderer} that periodically updates debugging information displayed by a
* {@link TextView}.
*/
/* package */ class DebugTrackRenderer extends TrackRenderer implements Runnable {
private final TextView textView;
private final MediaCodecTrackRenderer renderer;
private final ChunkSampleSource videoSampleSource;
private volatile boolean pendingFailure;
private volatile long currentPositionUs;
public DebugTrackRenderer(TextView textView, MediaCodecTrackRenderer renderer) {
this(textView, renderer, null);
}
public DebugTrackRenderer(TextView textView, MediaCodecTrackRenderer renderer,
ChunkSampleSource videoSampleSource) {
this.textView = textView;
this.renderer = renderer;
this.videoSampleSource = videoSampleSource;
}
public void injectFailure() {
pendingFailure = true;
}
@Override
protected boolean isEnded() {
return true;
}
@Override
protected boolean isReady() {
return true;
}
@Override
protected int doPrepare() throws ExoPlaybackException {
maybeFail();
return STATE_PREPARED;
}
@Override
protected void doSomeWork(long timeUs) throws ExoPlaybackException {
maybeFail();
if (timeUs < currentPositionUs || timeUs > currentPositionUs + 1000000) {
currentPositionUs = timeUs;
textView.post(this);
}
}
@Override
public void run() {
textView.setText(getRenderString());
}
private String getRenderString() {
return "ms(" + (currentPositionUs / 1000) + "), " + getQualityString() +
", " + renderer.codecCounters.getDebugString();
}
private String getQualityString() {
Format format = videoSampleSource == null ? null : videoSampleSource.getFormat();
return format == null ? "null" : "height(" + format.height + "), itag(" + format.id + ")";
}
@Override
protected long getCurrentPositionUs() {
return currentPositionUs;
}
@Override
protected long getDurationUs() {
return TrackRenderer.MATCH_LONGEST;
}
@Override
protected long getBufferedPositionUs() {
return TrackRenderer.END_OF_TRACK;
}
@Override
protected void seekTo(long timeUs) {
currentPositionUs = timeUs;
}
private void maybeFail() throws ExoPlaybackException {
if (pendingFailure) {
pendingFailure = false;
throw new ExoPlaybackException("fail() was called on DebugTrackRenderer");
}
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.full.player;
import com.google.android.exoplayer.FrameworkSampleSource;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
import android.content.Context;
import android.media.MediaCodec;
import android.net.Uri;
import android.widget.TextView;
/**
* A {@link RendererBuilder} for streams that can be read using
* {@link android.media.MediaExtractor}.
*/
public class DefaultRendererBuilder implements RendererBuilder {
private final Context context;
private final Uri uri;
private final TextView debugTextView;
public DefaultRendererBuilder(Context context, Uri uri, TextView debugTextView) {
this.context = context;
this.uri = uri;
this.debugTextView = debugTextView;
}
@Override
public void buildRenderers(DemoPlayer player, RendererBuilderCallback callback) {
// Build the video and audio renderers.
FrameworkSampleSource sampleSource = new FrameworkSampleSource(context, uri, null, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
null, true, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000,
player.getMainHandler(), player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource,
null, true, player.getMainHandler(), player);
// Build the debug renderer.
TrackRenderer debugRenderer = debugTextView != null
? new DebugTrackRenderer(debugTextView, videoRenderer)
: null;
// Invoke the callback.
TrackRenderer[] renderers = new TrackRenderer[DemoPlayer.RENDERER_COUNT];
renderers[DemoPlayer.TYPE_VIDEO] = videoRenderer;
renderers[DemoPlayer.TYPE_AUDIO] = audioRenderer;
renderers[DemoPlayer.TYPE_DEBUG] = debugRenderer;
callback.onRenderers(null, null, renderers);
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.simple;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.Format;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.dash.DashMp4ChunkSource;
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionFetcher;
import com.google.android.exoplayer.dash.mpd.Period;
import com.google.android.exoplayer.dash.mpd.Representation;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.HttpDataSource;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import android.media.MediaCodec;
import android.os.AsyncTask;
import android.os.Handler;
import java.util.ArrayList;
/**
* A {@link RendererBuilder} for DASH VOD.
*/
/* package */ class DashVodRendererBuilder implements RendererBuilder,
ManifestCallback<MediaPresentationDescription> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 60;
private final SimplePlayerActivity playerActivity;
private final String userAgent;
private final String url;
private final String contentId;
private RendererBuilderCallback callback;
public DashVodRendererBuilder(SimplePlayerActivity playerActivity, String userAgent, String url,
String contentId) {
this.playerActivity = playerActivity;
this.userAgent = userAgent;
this.url = url;
this.contentId = contentId;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
this.callback = callback;
MediaPresentationDescriptionFetcher mpdFetcher = new MediaPresentationDescriptionFetcher(this);
mpdFetcher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url, contentId);
}
@Override
public void onManifestError(String contentId, Exception e) {
callback.onRenderersError(e);
}
@Override
public void onManifest(String contentId, MediaPresentationDescription manifest) {
Handler mainHandler = playerActivity.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Obtain Representations for playback.
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
Representation audioRepresentation = null;
ArrayList<Representation> videoRepresentationsList = new ArrayList<Representation>();
Period period = manifest.periods.get(0);
for (int i = 0; i < period.adaptationSets.size(); i++) {
AdaptationSet adaptationSet = period.adaptationSets.get(i);
int adaptationSetType = adaptationSet.type;
for (int j = 0; j < adaptationSet.representations.size(); j++) {
Representation representation = adaptationSet.representations.get(j);
if (audioRepresentation == null && adaptationSetType == AdaptationSet.TYPE_AUDIO) {
audioRepresentation = representation;
} else if (adaptationSetType == AdaptationSet.TYPE_VIDEO) {
Format format = representation.format;
if (format.width * format.height <= maxDecodableFrameSize) {
videoRepresentationsList.add(representation);
} else {
// The device isn't capable of playing this stream.
}
}
}
}
Representation[] videoRepresentations = new Representation[videoRepresentationsList.size()];
videoRepresentationsList.toArray(videoRepresentations);
// Build the video renderer.
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
bandwidthMeter);
ChunkSource videoChunkSource = new DashMp4ChunkSource(videoDataSource,
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
// Build the audio renderer.
DataSource audioDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
bandwidthMeter);
ChunkSource audioChunkSource = new DashMp4ChunkSource(audioDataSource,
new FormatEvaluator.FixedEvaluator(), audioRepresentation);
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
audioSampleSource);
callback.onRenderers(videoRenderer, audioRenderer);
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.simple;
import com.google.android.exoplayer.FrameworkSampleSource;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import android.media.MediaCodec;
import android.net.Uri;
/**
* A {@link RendererBuilder} for streams that can be read using
* {@link android.media.MediaExtractor}.
*/
/* package */ class DefaultRendererBuilder implements RendererBuilder {
private final SimplePlayerActivity playerActivity;
private final Uri uri;
public DefaultRendererBuilder(SimplePlayerActivity playerActivity, Uri uri) {
this.playerActivity = playerActivity;
this.uri = uri;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
// Build the video and audio renderers.
FrameworkSampleSource sampleSource = new FrameworkSampleSource(playerActivity, uri, null, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
playerActivity, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
// Invoke the callback.
callback.onRenderers(videoRenderer, audioRenderer);
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.simple;
import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.ExoPlayer;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.VideoSurfaceView;
import com.google.android.exoplayer.demo.DemoUtil;
import com.google.android.exoplayer.demo.R;
import com.google.android.exoplayer.util.PlayerControl;
import android.app.Activity;
import android.content.Intent;
import android.media.MediaCodec.CryptoException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.MediaController;
import android.widget.Toast;
/**
* An activity that plays media using {@link ExoPlayer}.
*/
public class SimplePlayerActivity extends Activity implements SurfaceHolder.Callback,
ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener {
/**
* Builds renderers for the player.
*/
public interface RendererBuilder {
void buildRenderers(RendererBuilderCallback callback);
}
public static final int RENDERER_COUNT = 2;
public static final int TYPE_VIDEO = 0;
public static final int TYPE_AUDIO = 1;
private static final String TAG = "PlayerActivity";
public static final int TYPE_DASH_VOD = 0;
public static final int TYPE_SS_VOD = 1;
public static final int TYPE_OTHER = 2;
private MediaController mediaController;
private Handler mainHandler;
private View shutterView;
private VideoSurfaceView surfaceView;
private ExoPlayer player;
private RendererBuilder builder;
private RendererBuilderCallback callback;
private MediaCodecVideoTrackRenderer videoRenderer;
private boolean autoPlay = true;
private int playerPosition;
private Uri contentUri;
private int contentType;
private String contentId;
// Activity lifecycle
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
contentUri = intent.getData();
contentType = intent.getIntExtra(DemoUtil.CONTENT_TYPE_EXTRA, TYPE_OTHER);
contentId = intent.getStringExtra(DemoUtil.CONTENT_ID_EXTRA);
mainHandler = new Handler(getMainLooper());
builder = getRendererBuilder();
setContentView(R.layout.player_activity_simple);
View root = findViewById(R.id.root);
root.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
if (arg1.getAction() == MotionEvent.ACTION_DOWN) {
toggleControlsVisibility();
}
return true;
}
});
mediaController = new MediaController(this);
mediaController.setAnchorView(root);
shutterView = findViewById(R.id.shutter);
surfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
surfaceView.getHolder().addCallback(this);
}
@Override
public void onResume() {
super.onResume();
// Setup the player
player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000);
player.addListener(this);
player.seekTo(playerPosition);
// Build the player controls
mediaController.setMediaPlayer(new PlayerControl(player));
mediaController.setEnabled(true);
// Request the renderers
callback = new RendererBuilderCallback();
builder.buildRenderers(callback);
}
@Override
public void onPause() {
super.onPause();
// Release the player
if (player != null) {
playerPosition = player.getCurrentPosition();
player.release();
player = null;
}
callback = null;
videoRenderer = null;
shutterView.setVisibility(View.VISIBLE);
}
// Public methods
public Handler getMainHandler() {
return mainHandler;
}
// Internal methods
private void toggleControlsVisibility() {
if (mediaController.isShowing()) {
mediaController.hide();
} else {
mediaController.show(0);
}
}
private RendererBuilder getRendererBuilder() {
String userAgent = DemoUtil.getUserAgent(this);
switch (contentType) {
case TYPE_SS_VOD:
return new SmoothStreamingRendererBuilder(this, userAgent, contentUri.toString(),
contentId);
case TYPE_DASH_VOD:
return new DashVodRendererBuilder(this, userAgent, contentUri.toString(), contentId);
default:
return new DefaultRendererBuilder(this, contentUri);
}
}
private void onRenderers(RendererBuilderCallback callback,
MediaCodecVideoTrackRenderer videoRenderer, MediaCodecAudioTrackRenderer audioRenderer) {
if (this.callback != callback) {
return;
}
this.callback = null;
this.videoRenderer = videoRenderer;
player.prepare(videoRenderer, audioRenderer);
maybeStartPlayback();
}
private void maybeStartPlayback() {
Surface surface = surfaceView.getHolder().getSurface();
if (videoRenderer == null || surface == null || !surface.isValid()) {
// We're not ready yet.
return;
}
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
if (autoPlay) {
player.setPlayWhenReady(true);
autoPlay = false;
}
}
private void onRenderersError(RendererBuilderCallback callback, Exception e) {
if (this.callback != callback) {
return;
}
this.callback = null;
onError(e);
}
private void onError(Exception e) {
Log.e(TAG, "Playback failed", e);
Toast.makeText(this, R.string.failed, Toast.LENGTH_SHORT).show();
finish();
}
// ExoPlayer.Listener implementation
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
// Do nothing.
}
@Override
public void onPlayWhenReadyCommitted() {
// Do nothing.
}
@Override
public void onPlayerError(ExoPlaybackException e) {
onError(e);
}
// MediaCodecVideoTrackRenderer.Listener
@Override
public void onVideoSizeChanged(int width, int height) {
surfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (float) width / height);
}
@Override
public void onDrawnToSurface(Surface surface) {
shutterView.setVisibility(View.GONE);
}
@Override
public void onDroppedFrames(int count, long elapsed) {
Log.d(TAG, "Dropped frames: " + count);
}
@Override
public void onDecoderInitializationError(DecoderInitializationException e) {
// This is for informational purposes only. Do nothing.
}
@Override
public void onCryptoError(CryptoException e) {
// This is for informational purposes only. Do nothing.
}
// SurfaceHolder.Callback implementation
@Override
public void surfaceCreated(SurfaceHolder holder) {
maybeStartPlayback();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Do nothing.
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (videoRenderer != null) {
player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, null);
}
}
/* package */ final class RendererBuilderCallback {
public void onRenderers(MediaCodecVideoTrackRenderer videoRenderer,
MediaCodecAudioTrackRenderer audioRenderer) {
SimplePlayerActivity.this.onRenderers(this, videoRenderer, audioRenderer);
}
public void onRenderersError(Exception e) {
SimplePlayerActivity.this.onRenderersError(this, e);
}
}
}
/*
* Copyright (C) 2014 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.exoplayer.demo.simple;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecUtil;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.chunk.ChunkSampleSource;
import com.google.android.exoplayer.chunk.ChunkSource;
import com.google.android.exoplayer.chunk.FormatEvaluator;
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder;
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingChunkSource;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement;
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestFetcher;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.HttpDataSource;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
import android.media.MediaCodec;
import android.os.Handler;
import java.util.ArrayList;
/**
* A {@link RendererBuilder} for SmoothStreaming.
*/
/* package */ class SmoothStreamingRendererBuilder implements RendererBuilder,
ManifestCallback<SmoothStreamingManifest> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private static final int AUDIO_BUFFER_SEGMENTS = 60;
private final SimplePlayerActivity playerActivity;
private final String userAgent;
private final String url;
private final String contentId;
private RendererBuilderCallback callback;
public SmoothStreamingRendererBuilder(SimplePlayerActivity playerActivity, String userAgent,
String url, String contentId) {
this.playerActivity = playerActivity;
this.userAgent = userAgent;
this.url = url;
this.contentId = contentId;
}
@Override
public void buildRenderers(RendererBuilderCallback callback) {
this.callback = callback;
SmoothStreamingManifestFetcher mpdFetcher = new SmoothStreamingManifestFetcher(this);
mpdFetcher.execute(url + "/Manifest", contentId);
}
@Override
public void onManifestError(String contentId, Exception e) {
callback.onRenderersError(e);
}
@Override
public void onManifest(String contentId, SmoothStreamingManifest manifest) {
Handler mainHandler = playerActivity.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
// Obtain stream elements for playback.
int maxDecodableFrameSize = MediaCodecUtil.maxH264DecodableFrameSize();
int audioStreamElementIndex = -1;
int videoStreamElementIndex = -1;
ArrayList<Integer> videoTrackIndexList = new ArrayList<Integer>();
for (int i = 0; i < manifest.streamElements.length; i++) {
if (audioStreamElementIndex == -1
&& manifest.streamElements[i].type == StreamElement.TYPE_AUDIO) {
audioStreamElementIndex = i;
} else if (videoStreamElementIndex == -1
&& manifest.streamElements[i].type == StreamElement.TYPE_VIDEO) {
videoStreamElementIndex = i;
StreamElement streamElement = manifest.streamElements[i];
for (int j = 0; j < streamElement.tracks.length; j++) {
TrackElement trackElement = streamElement.tracks[j];
if (trackElement.maxWidth * trackElement.maxHeight <= maxDecodableFrameSize) {
videoTrackIndexList.add(j);
} else {
// The device isn't capable of playing this stream.
}
}
}
}
int[] videoTrackIndices = new int[videoTrackIndexList.size()];
for (int i = 0; i < videoTrackIndexList.size(); i++) {
videoTrackIndices[i] = videoTrackIndexList.get(i);
}
// Build the video renderer.
DataSource videoDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
bandwidthMeter);
ChunkSource videoChunkSource = new SmoothStreamingChunkSource(url, manifest,
videoStreamElementIndex, videoTrackIndices, videoDataSource,
new AdaptiveEvaluator(bandwidthMeter));
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(videoSampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, mainHandler, playerActivity, 50);
// Build the audio renderer.
DataSource audioDataSource = new HttpDataSource(userAgent, HttpDataSource.REJECT_PAYWALL_TYPES,
bandwidthMeter);
ChunkSource audioChunkSource = new SmoothStreamingChunkSource(url, manifest,
audioStreamElementIndex, new int[] {0}, audioDataSource,
new FormatEvaluator.FixedEvaluator());
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(
audioSampleSource);
callback.onRenderers(videoRenderer, audioRenderer);
}
}
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system use,
# "ant.properties", and override values to adapt the script to your
# project structure.
# Project target.
target=android-19
android.library=false
android.library.reference.1=../../../library/src/main
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2014 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<com.google.android.exoplayer.VideoSurfaceView android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<TextView android:id="@+id/subtitles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center|bottom"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="32dp"
android:gravity="center"
android:textSize="20sp"
android:visibility="invisible"/>
<View android:id="@+id/shutter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#88000000"
android:orientation="vertical">
<TextView android:id="@+id/player_state_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textSize="10sp"/>
<TextView android:id="@+id/debug_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textSize="10sp"/>
<LinearLayout android:id="@+id/controls_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<Button android:id="@+id/video_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video"
style="@style/DemoButton"
android:visibility="gone"
android:onClick="showVideoPopup"/>
<Button android:id="@+id/audio_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/audio"
style="@style/DemoButton"
android:visibility="gone"
android:onClick="showAudioPopup"/>
<Button android:id="@+id/text_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text"
style="@style/DemoButton"
android:visibility="gone"
android:onClick="showTextPopup"/>
<Button android:id="@+id/verbose_log_controls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/logging"
style="@style/DemoButton"
android:onClick="showVerboseLogPopup"/>
<Button android:id="@+id/retry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/retry"
android:visibility="gone"
style="@style/DemoButton"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2014 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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
<com.google.android.exoplayer.VideoSurfaceView android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"/>
<View android:id="@+id/shutter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
</FrameLayout>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2014 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView android:id="@+id/sample_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2014 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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textAllCaps="true"
android:textColor="@android:color/white"
android:textSize="14sp"
android:padding="8dp"
android:focusable="true"
android:background="#339999FF"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- The user visible name of the application. [CHAR LIMIT=20] -->
<string name="application_name">ExoPlayer Demo</string>
<string name="enable_background_audio">Play in background</string>
<string name="video">Video</string>
<string name="audio">Audio</string>
<string name="text">Text</string>
<string name="logging">Logging</string>
<string name="logging_normal">Normal</string>
<string name="logging_verbose">Verbose</string>
<string name="retry">Retry</string>
<string name="off">[off]</string>
<string name="on">[on]</string>
<string name="drm_not_supported">Protected content not supported on API levels below 18</string>
<string name="failed">Playback failed</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="RootTheme" parent="android:Theme.Holo">
</style>
<style name="PlayerTheme" parent="@style/RootTheme">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/black</item>
</style>
<style name="DemoButton">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:minWidth">40dp</item>
</style>
</resources>
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
No preview for this file type
#Tue Jun 10 20:02:28 BST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ExoPlayerLib</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1363908161147</id>
<name></name>
<type>22</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-false-false-BUILD</arguments>
</matcher>
</filter>
<filter>
<id>1363908161148</id>
<name></name>
<type>10</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-true-false-build</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>
// Copyright (C) 2014 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 plugin: 'android-library'
android {
compileSdkVersion 19
buildToolsVersion "19.1"
defaultConfig {
minSdkVersion 9
targetSdkVersion 19
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
}
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="src" path="java"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>ExoPlayerLib</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1363908161147</id>
<name></name>
<type>22</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-false-false-BUILD</arguments>
</matcher>
</filter>
<filter>
<id>1363908161148</id>
<name></name>
<type>10</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-true-false-build</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.source=1.6
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 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.exoplayer">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="19"/>
</manifest>
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Maintains codec event counts, for debugging purposes only.
*/
public final class CodecCounters {
public volatile long codecInitCount;
public volatile long codecReleaseCount;
public volatile long outputFormatChangedCount;
public volatile long outputBuffersChangedCount;
public volatile long queuedInputBufferCount;
public volatile long inputBufferWaitingForSampleCount;
public volatile long keyframeCount;
public volatile long queuedEndOfStreamCount;
public volatile long renderedOutputBufferCount;
public volatile long skippedOutputBufferCount;
public volatile long droppedOutputBufferCount;
public volatile long discardedSamplesCount;
/**
* Resets all counts to zero.
*/
public void zeroAllCounts() {
codecInitCount = 0;
codecReleaseCount = 0;
outputFormatChangedCount = 0;
outputBuffersChangedCount = 0;
queuedInputBufferCount = 0;
inputBufferWaitingForSampleCount = 0;
keyframeCount = 0;
queuedEndOfStreamCount = 0;
renderedOutputBufferCount = 0;
skippedOutputBufferCount = 0;
droppedOutputBufferCount = 0;
discardedSamplesCount = 0;
}
public String getDebugString() {
StringBuilder builder = new StringBuilder();
builder.append("cic(").append(codecInitCount).append(")");
builder.append("crc(").append(codecReleaseCount).append(")");
builder.append("ofc(").append(outputFormatChangedCount).append(")");
builder.append("obc(").append(outputBuffersChangedCount).append(")");
builder.append("qib(").append(queuedInputBufferCount).append(")");
builder.append("wib(").append(inputBufferWaitingForSampleCount).append(")");
builder.append("kfc(").append(keyframeCount).append(")");
builder.append("qes(").append(queuedEndOfStreamCount).append(")");
builder.append("ren(").append(renderedOutputBufferCount).append(")");
builder.append("sob(").append(skippedOutputBufferCount).append(")");
builder.append("dob(").append(droppedOutputBufferCount).append(")");
builder.append("dsc(").append(discardedSamplesCount).append(")");
return builder.toString();
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.MediaExtractor;
/**
* Compatibility wrapper around {@link android.media.MediaCodec.CryptoInfo}.
*/
public class CryptoInfo {
/**
* @see android.media.MediaCodec.CryptoInfo#iv
*/
public byte[] iv;
/**
* @see android.media.MediaCodec.CryptoInfo#key
*/
public byte[] key;
/**
* @see android.media.MediaCodec.CryptoInfo#mode
*/
public int mode;
/**
* @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData
*/
public int[] numBytesOfClearData;
/**
* @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData
*/
public int[] numBytesOfEncryptedData;
/**
* @see android.media.MediaCodec.CryptoInfo#numSubSamples
*/
public int numSubSamples;
private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo;
public CryptoInfo() {
frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null;
}
/**
* @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int)
*/
public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
byte[] key, byte[] iv, int mode) {
this.numSubSamples = numSubSamples;
this.numBytesOfClearData = numBytesOfClearData;
this.numBytesOfEncryptedData = numBytesOfEncryptedData;
this.key = key;
this.iv = iv;
this.mode = mode;
if (Util.SDK_INT >= 16) {
updateFrameworkCryptoInfoV16();
}
}
/**
* Equivalent to {@link MediaExtractor#getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo)}.
*
* @param extractor The extractor from which to retrieve the crypto information.
*/
@TargetApi(16)
public void setFromExtractorV16(MediaExtractor extractor) {
extractor.getSampleCryptoInfo(frameworkCryptoInfo);
numSubSamples = frameworkCryptoInfo.numSubSamples;
numBytesOfClearData = frameworkCryptoInfo.numBytesOfClearData;
numBytesOfEncryptedData = frameworkCryptoInfo.numBytesOfEncryptedData;
key = frameworkCryptoInfo.key;
iv = frameworkCryptoInfo.iv;
mode = frameworkCryptoInfo.mode;
}
/**
* Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
* <p>
* Successive calls to this method on a single {@link CryptoInfo} will return the same instance.
* Changes to the {@link CryptoInfo} will be reflected in the returned object. The return object
* should not be modified directly.
*
* @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance.
*/
@TargetApi(16)
public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() {
return frameworkCryptoInfo;
}
@TargetApi(16)
private android.media.MediaCodec.CryptoInfo newFrameworkCryptoInfoV16() {
return new android.media.MediaCodec.CryptoInfo();
}
@TargetApi(16)
private void updateFrameworkCryptoInfoV16() {
frameworkCryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, key, iv,
mode);
}
}
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Contains information about a media decoder.
*/
public final class DecoderInfo {
/**
* The name of the decoder.
* <p>
* May be passed to {@link android.media.MediaCodec#createByCodecName(String)} to create an
* instance of the decoder.
*/
public final String name;
/**
* Whether the decoder is adaptive.
*
* @see android.media.MediaCodecInfo.CodecCapabilities#isFeatureSupported(String)
* @see android.media.MediaCodecInfo.CodecCapabilities#FEATURE_AdaptivePlayback
*/
public final boolean adaptive;
/**
* @param name The name of the decoder.
* @param adaptive Whether the decoder is adaptive.
*/
/* package */ DecoderInfo(String name, boolean adaptive) {
this.name = name;
this.adaptive = adaptive;
}
}
/*
* Copyright (C) 2014 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.exoplayer;
/**
* A {@link TrackRenderer} that does nothing.
* <p>
* This renderer returns {@link TrackRenderer#STATE_IGNORE} from {@link #doPrepare()} in order to
* request that it should be ignored. {@link IllegalStateException} is thrown from all methods that
* are documented to indicate that they should not be invoked unless the renderer is prepared.
*/
public class DummyTrackRenderer extends TrackRenderer {
@Override
protected int doPrepare() throws ExoPlaybackException {
return STATE_IGNORE;
}
@Override
protected boolean isEnded() {
throw new IllegalStateException();
}
@Override
protected boolean isReady() {
throw new IllegalStateException();
}
@Override
protected void seekTo(long timeUs) {
throw new IllegalStateException();
}
@Override
protected void doSomeWork(long timeUs) {
throw new IllegalStateException();
}
@Override
protected long getDurationUs() {
throw new IllegalStateException();
}
@Override
protected long getBufferedPositionUs() {
throw new IllegalStateException();
}
@Override
protected long getCurrentPositionUs() {
throw new IllegalStateException();
}
}
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Thrown when a non-recoverable playback failure occurs.
* <p>
* Where possible, the cause returned by {@link #getCause()} will indicate the reason for failure.
*/
public class ExoPlaybackException extends Exception {
public ExoPlaybackException(String message) {
super(message);
}
public ExoPlaybackException(Throwable cause) {
super(cause);
}
public ExoPlaybackException(String message, Throwable cause) {
super(message, cause);
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Concrete implementation of {@link ExoPlayer}.
*/
/* package */ final class ExoPlayerImpl implements ExoPlayer {
private static final String TAG = "ExoPlayerImpl";
private final Handler eventHandler;
private final ExoPlayerImplInternal internalPlayer;
private final CopyOnWriteArraySet<Listener> listeners;
private final boolean[] rendererEnabledFlags;
private boolean playWhenReady;
private int playbackState;
private int pendingPlayWhenReadyAcks;
/**
* Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}.
*
* @param rendererCount The number of {@link TrackRenderer}s that will be passed to
* {@link #prepare(TrackRenderer[])}.
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
* or resume following a user action such as a seek.
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
* not due to a user action such as starting playback or seeking).
*/
@SuppressLint("HandlerLeak")
public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) {
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
this.playbackState = STATE_IDLE;
this.listeners = new CopyOnWriteArraySet<Listener>();
this.rendererEnabledFlags = new boolean[rendererCount];
for (int i = 0; i < rendererEnabledFlags.length; i++) {
rendererEnabledFlags[i] = true;
}
eventHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
ExoPlayerImpl.this.handleEvent(msg);
}
};
internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, rendererEnabledFlags,
minBufferMs, minRebufferMs);
}
@Override
public Looper getPlaybackLooper() {
return internalPlayer.getPlaybackLooper();
}
@Override
public void addListener(Listener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
listeners.remove(listener);
}
@Override
public int getPlaybackState() {
return playbackState;
}
@Override
public void prepare(TrackRenderer... renderers) {
internalPlayer.prepare(renderers);
}
@Override
public void setRendererEnabled(int index, boolean enabled) {
if (rendererEnabledFlags[index] != enabled) {
rendererEnabledFlags[index] = enabled;
internalPlayer.setRendererEnabled(index, enabled);
}
}
@Override
public boolean getRendererEnabled(int index) {
return rendererEnabledFlags[index];
}
@Override
public void setPlayWhenReady(boolean playWhenReady) {
if (this.playWhenReady != playWhenReady) {
this.playWhenReady = playWhenReady;
pendingPlayWhenReadyAcks++;
internalPlayer.setPlayWhenReady(playWhenReady);
for (Listener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
}
}
@Override
public boolean getPlayWhenReady() {
return playWhenReady;
}
@Override
public boolean isPlayWhenReadyCommitted() {
return pendingPlayWhenReadyAcks == 0;
}
@Override
public void seekTo(int positionMs) {
internalPlayer.seekTo(positionMs);
}
@Override
public void stop() {
internalPlayer.stop();
}
@Override
public void release() {
internalPlayer.release();
eventHandler.removeCallbacksAndMessages(null);
}
@Override
public void sendMessage(ExoPlayerComponent target, int messageType, Object message) {
internalPlayer.sendMessage(target, messageType, message);
}
@Override
public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message) {
internalPlayer.blockingSendMessage(target, messageType, message);
}
@Override
public int getDuration() {
return internalPlayer.getDuration();
}
@Override
public int getCurrentPosition() {
return internalPlayer.getCurrentPosition();
}
@Override
public int getBufferedPosition() {
return internalPlayer.getBufferedPosition();
}
@Override
public int getBufferedPercentage() {
int bufferedPosition = getBufferedPosition();
int duration = getDuration();
return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0
: (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
}
// Not private so it can be called from an inner class without going through a thunk method.
/* package */ void handleEvent(Message msg) {
switch (msg.what) {
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
playbackState = msg.arg1;
for (Listener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackState);
}
break;
}
case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
pendingPlayWhenReadyAcks--;
if (pendingPlayWhenReadyAcks == 0) {
for (Listener listener : listeners) {
listener.onPlayWhenReadyCommitted();
}
}
break;
}
case ExoPlayerImplInternal.MSG_ERROR: {
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
for (Listener listener : listeners) {
listener.onPlayerError(exception);
}
break;
}
}
}
}
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Information about the ExoPlayer library.
*/
// TODO: This file should be automatically generated by the build system.
public class ExoPlayerLibraryInfo {
private ExoPlayerLibraryInfo() {}
/**
* The version of the library, expressed as a string.
*/
public static final String VERSION = "1.0.10";
/**
* The version of the library, expressed as an integer.
* <p>
* Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the
* corresponding integer version 1002003.
*/
public static final int VERSION_INT = 1000010;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer.util.Assertions}
* checks enabled.
*/
public static final boolean ASSERTIONS_ENABLED = true;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer.util.TraceUtil}
* trace enabled.
*/
public static final boolean TRACE_ENABLED = true;
}
/*
* Copyright (C) 2014 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.exoplayer;
import java.util.Map;
import java.util.UUID;
/**
* Holds a {@link MediaFormat} and corresponding drm scheme initialization data.
*/
public final class FormatHolder {
/**
* The format of the media.
*/
public MediaFormat format;
/**
* Initialization data for each of the drm schemes supported by the media, keyed by scheme UUID.
* Null if the media is not encrypted.
*/
public Map<UUID, byte[]> drmInitData;
}
/*
* Copyright (C) 2014 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.exoplayer;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaExtractor;
import android.net.Uri;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
/**
* Extracts samples from a stream using Android's {@link MediaExtractor}.
*/
// TODO: This implementation needs to be fixed so that its methods are non-blocking (either
// through use of a background thread, or through changes to the framework's MediaExtractor API).
@TargetApi(16)
public final class FrameworkSampleSource implements SampleSource {
private static final int TRACK_STATE_DISABLED = 0;
private static final int TRACK_STATE_ENABLED = 1;
private static final int TRACK_STATE_FORMAT_SENT = 2;
private final Context context;
private final Uri uri;
private final Map<String, String> headers;
private MediaExtractor extractor;
private TrackInfo[] trackInfos;
private boolean prepared;
private int remainingReleaseCount;
private int[] trackStates;
private boolean[] pendingDiscontinuities;
private long seekTimeUs;
public FrameworkSampleSource(Context context, Uri uri, Map<String, String> headers,
int downstreamRendererCount) {
Assertions.checkState(Util.SDK_INT >= 16);
this.context = context;
this.uri = uri;
this.headers = headers;
this.remainingReleaseCount = downstreamRendererCount;
}
@Override
public boolean prepare() throws IOException {
if (!prepared) {
extractor = new MediaExtractor();
extractor.setDataSource(context, uri, headers);
trackStates = new int[extractor.getTrackCount()];
pendingDiscontinuities = new boolean[extractor.getTrackCount()];
trackInfos = new TrackInfo[trackStates.length];
for (int i = 0; i < trackStates.length; i++) {
android.media.MediaFormat format = extractor.getTrackFormat(i);
long duration = format.containsKey(android.media.MediaFormat.KEY_DURATION) ?
format.getLong(android.media.MediaFormat.KEY_DURATION) : TrackRenderer.UNKNOWN_TIME;
String mime = format.getString(android.media.MediaFormat.KEY_MIME);
trackInfos[i] = new TrackInfo(mime, duration);
}
prepared = true;
}
return true;
}
@Override
public int getTrackCount() {
Assertions.checkState(prepared);
return extractor.getTrackCount();
}
@Override
public TrackInfo getTrackInfo(int track) {
Assertions.checkState(prepared);
return trackInfos[track];
}
@Override
public void enable(int track, long timeUs) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED);
boolean wasSourceEnabled = isEnabled();
trackStates[track] = TRACK_STATE_ENABLED;
extractor.selectTrack(track);
if (!wasSourceEnabled) {
seekToUs(timeUs);
}
}
@Override
public void continueBuffering(long playbackPositionUs) {
// Do nothing. The MediaExtractor instance is responsible for buffering.
}
@Override
public int readData(int track, long playbackPositionUs, FormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
if (pendingDiscontinuities[track]) {
pendingDiscontinuities[track] = false;
return DISCONTINUITY_READ;
}
if (onlyReadDiscontinuity) {
return NOTHING_READ;
}
int extractorTrackIndex = extractor.getSampleTrackIndex();
if (extractorTrackIndex == track) {
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
formatHolder.format = MediaFormat.createFromFrameworkMediaFormatV16(
extractor.getTrackFormat(track));
formatHolder.drmInitData = Util.SDK_INT >= 18 ? getPsshInfoV18() : null;
trackStates[track] = TRACK_STATE_FORMAT_SENT;
return FORMAT_READ;
}
if (sampleHolder.data != null) {
int offset = sampleHolder.data.position();
sampleHolder.size = extractor.readSampleData(sampleHolder.data, offset);
sampleHolder.data.position(offset + sampleHolder.size);
} else {
sampleHolder.size = 0;
}
sampleHolder.timeUs = extractor.getSampleTime();
sampleHolder.flags = extractor.getSampleFlags();
if ((sampleHolder.flags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
sampleHolder.cryptoInfo.setFromExtractorV16(extractor);
}
seekTimeUs = -1;
extractor.advance();
return SAMPLE_READ;
} else {
return extractorTrackIndex < 0 ? END_OF_STREAM : NOTHING_READ;
}
}
@TargetApi(18)
private Map<UUID, byte[]> getPsshInfoV18() {
Map<UUID, byte[]> psshInfo = extractor.getPsshInfo();
return (psshInfo == null || psshInfo.isEmpty()) ? null : psshInfo;
}
@Override
public void disable(int track) {
Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
extractor.unselectTrack(track);
pendingDiscontinuities[track] = false;
trackStates[track] = TRACK_STATE_DISABLED;
}
@Override
public void seekToUs(long timeUs) {
Assertions.checkState(prepared);
if (seekTimeUs != timeUs) {
// Avoid duplicate calls to the underlying extractor's seek method in the case that there
// have been no interleaving calls to advance.
seekTimeUs = timeUs;
extractor.seekTo(timeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
for (int i = 0; i < trackStates.length; ++i) {
if (trackStates[i] != TRACK_STATE_DISABLED) {
pendingDiscontinuities[i] = true;
}
}
}
}
@Override
public long getBufferedPositionUs() {
Assertions.checkState(prepared);
long bufferedDurationUs = extractor.getCachedDuration();
if (bufferedDurationUs == -1) {
return TrackRenderer.UNKNOWN_TIME;
} else {
return extractor.getSampleTime() + bufferedDurationUs;
}
}
@Override
public void release() {
Assertions.checkState(remainingReleaseCount > 0);
if (--remainingReleaseCount == 0) {
extractor.release();
extractor = null;
}
}
private boolean isEnabled() {
for (int i = 0; i < trackStates.length; i++) {
if (trackStates[i] != TRACK_STATE_DISABLED) {
return true;
}
}
return false;
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import com.google.android.exoplayer.upstream.Allocator;
/**
* Coordinates multiple loaders of time series data.
*/
public interface LoadControl {
/**
* Registers a loader.
*
* @param loader The loader being registered.
* @param bufferSizeContribution For controls whose {@link Allocator}s maintain a pool of memory
* for the purpose of satisfying allocation requests, this is a hint indicating the loader's
* desired contribution to the size of the pool, in bytes.
*/
void register(Object loader, int bufferSizeContribution);
/**
* Unregisters a loader.
*
* @param loader The loader being unregistered.
*/
void unregister(Object loader);
/**
* Gets the {@link Allocator} that loaders should use to obtain memory allocations into which
* data can be loaded.
*
* @return The {@link Allocator} to use.
*/
Allocator getAllocator();
/**
* Hints to the control that it should consider trimming any unused memory being held in order
* to satisfy allocation requests.
* <p>
* This method is typically invoked by a recently unregistered loader, once it has released all
* of its allocations back to the {@link Allocator}.
*/
void trimAllocator();
/**
* Invoked by a loader to update the control with its current state.
* <p>
* This method must be called by a registered loader whenever its state changes. This is true
* even if the registered loader does not itself wish to start its next load (since the state of
* the loader will still affect whether other registered loaders are allowed to proceed).
*
* @param loader The loader invoking the update.
* @param playbackPositionUs The loader's playback position.
* @param nextLoadPositionUs The loader's next load position, or -1 if finished.
* @param loading Whether the loader is currently loading data.
* @param failed Whether the loader has failed, meaning it does not wish to load more data.
* @return True if the loader is allowed to start its next load. False otherwise.
*/
boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs,
boolean loading, boolean failed);
}
/*
* Copyright (C) 2014 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.exoplayer;
import android.os.SystemClock;
/**
* A simple clock for tracking the progression of media time. The clock can be started, stopped and
* its time can be set and retrieved. When started, this clock is based on
* {@link SystemClock#elapsedRealtime()}.
*/
/* package */ class MediaClock {
private boolean started;
/**
* The media time when the clock was last set or stopped.
*/
private long timeUs;
/**
* The difference between {@link SystemClock#elapsedRealtime()} and {@link #timeUs}
* when the clock was last set or started.
*/
private long deltaUs;
/**
* Starts the clock. Does nothing if the clock is already started.
*/
public void start() {
if (!started) {
started = true;
deltaUs = elapsedRealtimeMinus(timeUs);
}
}
/**
* Stops the clock. Does nothing if the clock is already stopped.
*/
public void stop() {
if (started) {
timeUs = elapsedRealtimeMinus(deltaUs);
started = false;
}
}
/**
* @param timeUs The time to set in microseconds.
*/
public void setTimeUs(long timeUs) {
this.timeUs = timeUs;
deltaUs = elapsedRealtimeMinus(timeUs);
}
/**
* @return The current time in microseconds.
*/
public long getTimeUs() {
return started ? elapsedRealtimeMinus(deltaUs) : timeUs;
}
private long elapsedRealtimeMinus(long microSeconds) {
return SystemClock.elapsedRealtime() * 1000 - microSeconds;
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.util.Pair;
import java.util.HashMap;
/**
* A utility class for querying the available codecs.
*/
@TargetApi(16)
public class MediaCodecUtil {
private static final HashMap<String, Pair<MediaCodecInfo, CodecCapabilities>> codecs =
new HashMap<String, Pair<MediaCodecInfo, CodecCapabilities>>();
/**
* Get information about the decoder that will be used for a given mime type. If no decoder
* exists for the mime type then null is returned.
*
* @param mimeType The mime type.
* @return Information about the decoder that will be used, or null if no decoder exists.
*/
public static DecoderInfo getDecoderInfo(String mimeType) {
Pair<MediaCodecInfo, CodecCapabilities> info = getMediaCodecInfo(mimeType);
if (info == null) {
return null;
}
return new DecoderInfo(info.first.getName(), isAdaptive(info.second));
}
/**
* Optional call to warm the codec cache. Call from any appropriate
* place to hide latency.
*/
public static synchronized void warmCodecs(String[] mimeTypes) {
for (int i = 0; i < mimeTypes.length; i++) {
getMediaCodecInfo(mimeTypes[i]);
}
}
/**
* Returns the best decoder and its capabilities for the given mimeType. If there's no decoder
* returns null.
*/
private static synchronized Pair<MediaCodecInfo, CodecCapabilities> getMediaCodecInfo(
String mimeType) {
Pair<MediaCodecInfo, CodecCapabilities> result = codecs.get(mimeType);
if (result != null) {
return result;
}
int numberOfCodecs = MediaCodecList.getCodecCount();
// Note: MediaCodecList is sorted by the framework such that the best decoders come first.
for (int i = 0; i < numberOfCodecs; i++) {
MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
String codecName = info.getName();
if (!info.isEncoder() && isOmxCodec(codecName)) {
String[] supportedTypes = info.getSupportedTypes();
for (int j = 0; j < supportedTypes.length; j++) {
String supportedType = supportedTypes[j];
if (supportedType.equalsIgnoreCase(mimeType)) {
result = Pair.create(info, info.getCapabilitiesForType(supportedType));
codecs.put(mimeType, result);
return result;
}
}
}
}
return null;
}
private static boolean isOmxCodec(String name) {
return name.startsWith("OMX.");
}
private static boolean isAdaptive(CodecCapabilities capabilities) {
if (Util.SDK_INT >= 19) {
return isAdaptiveV19(capabilities);
} else {
return false;
}
}
@TargetApi(19)
private static boolean isAdaptiveV19(CodecCapabilities capabilities) {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
}
/**
* @param profile An AVC profile constant from {@link CodecProfileLevel}.
* @param level An AVC profile level from {@link CodecProfileLevel}.
* @return Whether the specified profile is supported at the specified level.
*/
public static boolean isH264ProfileSupported(int profile, int level) {
Pair<MediaCodecInfo, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264);
if (info == null) {
return false;
}
CodecCapabilities capabilities = info.second;
for (int i = 0; i < capabilities.profileLevels.length; i++) {
CodecProfileLevel profileLevel = capabilities.profileLevels[i];
if (profileLevel.profile == profile && profileLevel.level >= level) {
return true;
}
}
return false;
}
/**
* @return the maximum frame size for an H264 stream that can be decoded on the device.
*/
public static int maxH264DecodableFrameSize() {
Pair<MediaCodecInfo, CodecCapabilities> info = getMediaCodecInfo(MimeTypes.VIDEO_H264);
if (info == null) {
return 0;
}
int maxH264DecodableFrameSize = 0;
CodecCapabilities capabilities = info.second;
for (int i = 0; i < capabilities.profileLevels.length; i++) {
CodecProfileLevel profileLevel = capabilities.profileLevels[i];
maxH264DecodableFrameSize = Math.max(
avcLevelToMaxFrameSize(profileLevel.level), maxH264DecodableFrameSize);
}
return maxH264DecodableFrameSize;
}
/**
* Conversion values taken from: https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC.
*
* @param avcLevel one of CodecProfileLevel.AVCLevel* constants.
* @return maximum frame size that can be decoded by a decoder with the specified avc level
* (or {@code -1} if the level is not recognized)
*/
private static int avcLevelToMaxFrameSize(int avcLevel) {
switch (avcLevel) {
case CodecProfileLevel.AVCLevel1: return 25344;
case CodecProfileLevel.AVCLevel1b: return 25344;
case CodecProfileLevel.AVCLevel12: return 101376;
case CodecProfileLevel.AVCLevel13: return 101376;
case CodecProfileLevel.AVCLevel2: return 101376;
case CodecProfileLevel.AVCLevel21: return 202752;
case CodecProfileLevel.AVCLevel22: return 414720;
case CodecProfileLevel.AVCLevel3: return 414720;
case CodecProfileLevel.AVCLevel31: return 921600;
case CodecProfileLevel.AVCLevel32: return 1310720;
case CodecProfileLevel.AVCLevel4: return 2097152;
case CodecProfileLevel.AVCLevel41: return 2097152;
case CodecProfileLevel.AVCLevel42: return 2228224;
case CodecProfileLevel.AVCLevel5: return 5652480;
case CodecProfileLevel.AVCLevel51: return 9437184;
default: return -1;
}
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import com.google.android.exoplayer.util.Util;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Encapsulates the information describing the format of media data, be it audio or video.
*/
public class MediaFormat {
public static final int NO_VALUE = -1;
public final String mimeType;
public final int maxInputSize;
public final int width;
public final int height;
public final int channelCount;
public final int sampleRate;
private int maxWidth;
private int maxHeight;
public final List<byte[]> initializationData;
// Lazy-initialized hashcode.
private int hashCode;
// Possibly-lazy-initialized framework media format.
private android.media.MediaFormat frameworkMediaFormat;
@TargetApi(16)
public static MediaFormat createFromFrameworkMediaFormatV16(android.media.MediaFormat format) {
return new MediaFormat(format);
}
public static MediaFormat createVideoFormat(String mimeType, int maxInputSize, int width,
int height, List<byte[]> initializationData) {
return new MediaFormat(mimeType, maxInputSize, width, height, NO_VALUE, NO_VALUE,
initializationData);
}
public static MediaFormat createAudioFormat(String mimeType, int maxInputSize, int channelCount,
int sampleRate, List<byte[]> initializationData) {
return new MediaFormat(mimeType, maxInputSize, NO_VALUE, NO_VALUE, channelCount, sampleRate,
initializationData);
}
@TargetApi(16)
private MediaFormat(android.media.MediaFormat format) {
this.frameworkMediaFormat = format;
mimeType = format.getString(android.media.MediaFormat.KEY_MIME);
maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE);
width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH);
height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT);
channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT);
sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE);
initializationData = new ArrayList<byte[]>();
for (int i = 0; format.containsKey("csd-" + i); i++) {
ByteBuffer buffer = format.getByteBuffer("csd-" + i);
byte[] data = new byte[buffer.limit()];
buffer.get(data);
initializationData.add(data);
buffer.flip();
}
maxWidth = NO_VALUE;
maxHeight = NO_VALUE;
}
private MediaFormat(String mimeType, int maxInputSize, int width, int height, int channelCount,
int sampleRate, List<byte[]> initializationData) {
this.mimeType = mimeType;
this.maxInputSize = maxInputSize;
this.width = width;
this.height = height;
this.channelCount = channelCount;
this.sampleRate = sampleRate;
this.initializationData = initializationData == null ? Collections.<byte[]>emptyList()
: initializationData;
maxWidth = NO_VALUE;
maxHeight = NO_VALUE;
}
public void setMaxVideoDimensions(int maxWidth, int maxHeight) {
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
if (frameworkMediaFormat != null) {
maybeSetMaxDimensionsV16(frameworkMediaFormat);
}
}
public int getMaxVideoWidth() {
return maxWidth;
}
public int getMaxVideoHeight() {
return maxHeight;
}
@Override
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 31 * result + mimeType == null ? 0 : mimeType.hashCode();
result = 31 * result + maxInputSize;
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + maxWidth;
result = 31 * result + maxHeight;
result = 31 * result + channelCount;
result = 31 * result + sampleRate;
for (int i = 0; i < initializationData.size(); i++) {
result = 31 * result + Arrays.hashCode(initializationData.get(i));
}
hashCode = result;
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
MediaFormat other = (MediaFormat) obj;
if (maxInputSize != other.maxInputSize || width != other.width || height != other.height ||
maxWidth != other.maxWidth || maxHeight != other.maxHeight ||
channelCount != other.channelCount || sampleRate != other.sampleRate ||
!Util.areEqual(mimeType, other.mimeType) ||
initializationData.size() != other.initializationData.size()) {
return false;
}
for (int i = 0; i < initializationData.size(); i++) {
if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) {
return false;
}
}
return true;
}
@Override
public String toString() {
return "MediaFormat(" + mimeType + ", " + maxInputSize + ", " + width + ", " + height + ", " +
channelCount + ", " + sampleRate + ", " + maxWidth + ", " + maxHeight + ")";
}
/**
* @return A {@link MediaFormat} representation of this format.
*/
@TargetApi(16)
public final android.media.MediaFormat getFrameworkMediaFormatV16() {
if (frameworkMediaFormat == null) {
android.media.MediaFormat format = new android.media.MediaFormat();
format.setString(android.media.MediaFormat.KEY_MIME, mimeType);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate);
for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
}
maybeSetMaxDimensionsV16(format);
frameworkMediaFormat = format;
}
return frameworkMediaFormat;
}
@SuppressLint("InlinedApi")
@TargetApi(16)
private final void maybeSetMaxDimensionsV16(android.media.MediaFormat format) {
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth);
maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight);
}
@TargetApi(16)
private static final void maybeSetIntegerV16(android.media.MediaFormat format, String key,
int value) {
if (value != NO_VALUE) {
format.setInteger(key, value);
}
}
@TargetApi(16)
private static final int getOptionalIntegerV16(android.media.MediaFormat format,
String key) {
return format.containsKey(key) ? format.getInteger(key) : NO_VALUE;
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import java.io.IOException;
/**
* Thrown when an error occurs parsing media data.
*/
public class ParserException extends IOException {
public ParserException(String message) {
super(message);
}
public ParserException(Exception cause) {
super(cause);
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import java.nio.ByteBuffer;
/**
* Holds sample data and corresponding metadata.
*/
public final class SampleHolder {
/**
* Whether a {@link SampleSource} is permitted to replace {@link #data} if its current value is
* null or of insufficient size to hold the sample.
*/
public final boolean allowDataBufferReplacement;
public final CryptoInfo cryptoInfo;
/**
* A buffer holding the sample data.
*/
public ByteBuffer data;
/**
* The size of the sample in bytes.
*/
public int size;
/**
* Flags that accompany the sample. A combination of
* {@link android.media.MediaExtractor#SAMPLE_FLAG_SYNC} and
* {@link android.media.MediaExtractor#SAMPLE_FLAG_ENCRYPTED}
*/
public int flags;
/**
* The time at which the sample should be presented.
*/
public long timeUs;
/**
* If true then the sample should be decoded, but should not be presented.
*/
public boolean decodeOnly;
/**
* @param allowDataBufferReplacement See {@link #allowDataBufferReplacement}.
*/
public SampleHolder(boolean allowDataBufferReplacement) {
this.cryptoInfo = new CryptoInfo();
this.allowDataBufferReplacement = allowDataBufferReplacement;
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import java.io.IOException;
/**
* A source of media samples.
* <p>
* A {@link SampleSource} may expose one or multiple tracks. The number of tracks and information
* about each can be queried using {@link #getTrackCount()} and {@link #getTrackInfo(int)}
* respectively.
*/
public interface SampleSource {
/**
* The end of stream has been reached.
*/
public static final int END_OF_STREAM = -1;
/**
* Neither a sample nor a format was read in full. This may be because insufficient data is
* buffered upstream. If multiple tracks are enabled, this return value may indicate that the
* next piece of data to be returned from the {@link SampleSource} corresponds to a different
* track than the one for which data was requested.
*/
public static final int NOTHING_READ = -2;
/**
* A sample was read.
*/
public static final int SAMPLE_READ = -3;
/**
* A format was read.
*/
public static final int FORMAT_READ = -4;
/**
* A discontinuity in the sample stream.
*/
public static final int DISCONTINUITY_READ = -5;
/**
* Prepares the source.
* <p>
* Preparation may require reading from the data source (e.g. to determine the available tracks
* and formats). If insufficient data is available then the call will return rather than block.
* The method can be called repeatedly until the return value indicates success.
*
* @return True if the source was prepared successfully, false otherwise.
* @throws IOException If an error occurred preparing the source.
*/
public boolean prepare() throws IOException;
/**
* Returns the number of tracks exposed by the source.
*
* @return The number of tracks.
*/
public int getTrackCount();
/**
* Returns information about the specified track.
* <p>
* This method should not be called until after the source has been successfully prepared.
*
* @return Information about the specified track.
*/
public TrackInfo getTrackInfo(int track);
/**
* Enable the specified track. This allows the track's format and samples to be read from
* {@link #readData(int, long, FormatHolder, SampleHolder, boolean)}.
* <p>
* This method should not be called until after the source has been successfully prepared.
*
* @param track The track to enable.
* @param timeUs The player's current playback position.
*/
public void enable(int track, long timeUs);
/**
* Disable the specified track.
* <p>
* This method should not be called until after the source has been successfully prepared.
*
* @param track The track to disable.
*/
public void disable(int track);
/**
* Indicates to the source that it should still be buffering data.
*
* @param playbackPositionUs The current playback position.
*/
public void continueBuffering(long playbackPositionUs);
/**
* Attempts to read either a sample, a new format or or a discontinuity from the source.
* <p>
* This method should not be called until after the source has been successfully prepared.
* <p>
* Note that where multiple tracks are enabled, {@link #NOTHING_READ} may be returned if the
* next piece of data to be read from the {@link SampleSource} corresponds to a different track
* than the one for which data was requested.
*
* @param track The track from which to read.
* @param playbackPositionUs The current playback position.
* @param formatHolder A {@link FormatHolder} object to populate in the case of a new format.
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample. If
* the caller requires the sample data then it must ensure that {@link SampleHolder#data}
* references a valid output buffer.
* @param onlyReadDiscontinuity Whether to only read a discontinuity. If true, only
* {@link #DISCONTINUITY_READ} or {@link #NOTHING_READ} can be returned.
* @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ},
* {@link #DISCONTINUITY_READ}, {@link #NOTHING_READ} or {@link #END_OF_STREAM}.
* @throws IOException If an error occurred reading from the source.
*/
public int readData(int track, long playbackPositionUs, FormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException;
/**
* Seeks to the specified time in microseconds.
* <p>
* This method should not be called until after the source has been successfully prepared.
*
* @param timeUs The seek position in microseconds.
*/
public void seekToUs(long timeUs);
/**
* Returns an estimate of the position up to which data is buffered.
* <p>
* This method should not be called until after the source has been successfully prepared.
*
* @return An estimate of the absolute position in micro-seconds up to which data is buffered,
* or {@link TrackRenderer#END_OF_TRACK} if data is buffered to the end of the stream, or
* {@link TrackRenderer#UNKNOWN_TIME} if no estimate is available.
*/
public long getBufferedPositionUs();
/**
* Releases the {@link SampleSource}.
*/
public void release();
}
/*
* Copyright (C) 2014 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.exoplayer;
/**
* Holds high level information about a media track.
*/
public final class TrackInfo {
public final String mimeType;
public final long durationUs;
public TrackInfo(String mimeType, long durationUs) {
this.mimeType = mimeType;
this.durationUs = durationUs;
}
}
/*
* Copyright (C) 2014 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.exoplayer;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceView;
/**
* A SurfaceView that resizes itself to match a specified aspect ratio.
*/
public class VideoSurfaceView extends SurfaceView {
/**
* The surface view will not resize itself if the fractional difference between its default
* aspect ratio and the aspect ratio of the video falls below this threshold.
* <p>
* This tolerance is useful for fullscreen playbacks, since it ensures that the surface will
* occupy the whole of the screen when playing content that has the same (or virtually the same)
* aspect ratio as the device. This typically reduces the number of view layers that need to be
* composited by the underlying system, which can help to reduce power consumption.
*/
private static final float MAX_ASPECT_RATIO_DEFORMATION_PERCENT = 0.01f;
private float videoAspectRatio;
public VideoSurfaceView(Context context) {
super(context);
}
public VideoSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Set the aspect ratio that this {@link VideoSurfaceView} should satisfy.
*
* @param widthHeightRatio The width to height ratio.
*/
public void setVideoWidthHeightRatio(float widthHeightRatio) {
if (this.videoAspectRatio != widthHeightRatio) {
this.videoAspectRatio = widthHeightRatio;
requestLayout();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
if (videoAspectRatio != 0) {
float viewAspectRatio = (float) width / height;
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
if (aspectDeformation > MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
height = (int) (width / videoAspectRatio);
} else if (aspectDeformation < -MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
width = (int) (height * videoAspectRatio);
}
}
setMeasuredDimension(width, height);
}
}
/*
* Copyright (C) 2014 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.exoplayer.chunk;
import com.google.android.exoplayer.upstream.Allocation;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceStream;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.Assertions;
import java.io.IOException;
/**
* An abstract base class for {@link Loadable} implementations that load chunks of data required
* for the playback of streams.
*/
public abstract class Chunk implements Loadable {
/**
* The format associated with the data being loaded.
*/
// TODO: Consider removing this and pushing it down into MediaChunk instead.
public final Format format;
/**
* The reason for a {@link ChunkSource} having generated this chunk. For reporting only. Possible
* values for this variable are defined by the specific {@link ChunkSource} implementations.
*/
public final int trigger;
private final DataSource dataSource;
private final DataSpec dataSpec;
private DataSourceStream dataSourceStream;
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == DataSpec.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param format See {@link #format}.
* @param trigger See {@link #trigger}.
*/
public Chunk(DataSource dataSource, DataSpec dataSpec, Format format, int trigger) {
Assertions.checkState(dataSpec.length <= Integer.MAX_VALUE);
this.dataSource = Assertions.checkNotNull(dataSource);
this.dataSpec = Assertions.checkNotNull(dataSpec);
this.format = Assertions.checkNotNull(format);
this.trigger = trigger;
}
/**
* Initializes the {@link Chunk}.
*
* @param allocator An {@link Allocator} from which the {@link Allocation} needed to contain the
* data can be obtained.
*/
public final void init(Allocator allocator) {
Assertions.checkState(dataSourceStream == null);
dataSourceStream = new DataSourceStream(dataSource, dataSpec, allocator);
}
/**
* Releases the {@link Chunk}, releasing any backing {@link Allocation}s.
*/
public final void release() {
if (dataSourceStream != null) {
dataSourceStream.close();
dataSourceStream = null;
}
}
/**
* Gets the length of the chunk in bytes.
*
* @return The length of the chunk in bytes, or {@value DataSpec#LENGTH_UNBOUNDED} if the length
* has yet to be determined.
*/
public final long getLength() {
return dataSourceStream.getLength();
}
/**
* Whether the whole of the data has been consumed.
*
* @return True if the whole of the data has been consumed. False otherwise.
*/
public final boolean isReadFinished() {
return dataSourceStream.isEndOfStream();
}
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
public final boolean isLoadFinished() {
return dataSourceStream.isLoadFinished();
}
/**
* Gets the number of bytes that have been loaded.
*
* @return The number of bytes that have been loaded.
*/
public final long bytesLoaded() {
return dataSourceStream.getLoadPosition();
}
/**
* Causes loaded data to be consumed.
*
* @throws IOException If an error occurs consuming the loaded data.
*/
public final void consume() throws IOException {
Assertions.checkState(dataSourceStream != null);
consumeStream(dataSourceStream);
}
/**
* Returns a byte array containing the loaded data. If the chunk is partially loaded, this
* method returns the data that has been loaded so far. If nothing has been loaded, null is
* returned.
*
* @return The loaded data or null.
*/
public final byte[] getLoadedData() {
Assertions.checkState(dataSourceStream != null);
return dataSourceStream.getLoadedData();
}
/**
* Invoked by {@link #consume()}. Implementations may override this method if they wish to
* consume the loaded data at this point.
* <p>
* The default implementation is a no-op.
*
* @param stream The stream of loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected void consumeStream(NonBlockingInputStream stream) throws IOException {
// Do nothing.
}
protected final NonBlockingInputStream getNonBlockingInputStream() {
return dataSourceStream;
}
protected final void resetReadPosition() {
if (dataSourceStream != null) {
dataSourceStream.resetReadPosition();
} else {
// We haven't been initialized yet, so the read position must already be 0.
}
}
// Loadable implementation
@Override
public final void cancelLoad() {
dataSourceStream.cancelLoad();
}
@Override
public final boolean isLoadCanceled() {
return dataSourceStream.isLoadCanceled();
}
@Override
public final void load() throws IOException, InterruptedException {
dataSourceStream.load();
}
}
/*
* Copyright (C) 2014 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.exoplayer.chunk;
/**
* Holds a chunk operation, which consists of a {@link Chunk} to load together with the number of
* {@link MediaChunk}s that should be retained on the queue.
*/
public final class ChunkOperationHolder {
/**
* The number of {@link MediaChunk}s to retain in a queue.
*/
public int queueSize;
/**
* The chunk.
*/
public Chunk chunk;
}
/*
* Copyright (C) 2014 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.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.TrackInfo;
import java.io.IOException;
import java.util.List;
/**
* A provider of {@link Chunk}s for a {@link ChunkSampleSource} to load.
*/
/*
* TODO: Share more state between this interface and {@link ChunkSampleSource}. In particular
* implementations of this class needs to know about errors, and should be more tightly integrated
* into the process of resuming loading of a chunk after an error occurs.
*/
public interface ChunkSource {
/**
* Gets information about the track for which this instance provides {@link Chunk}s.
* <p>
* May be called when the source is disabled or enabled.
*
* @return Information about the track.
*/
TrackInfo getTrackInfo();
/**
* Adaptive video {@link ChunkSource} implementations must set the maximum video dimensions on
* the supplied {@link MediaFormat}. Other implementations do nothing.
* <p>
* Only called when the source is enabled.
*/
void getMaxVideoDimensions(MediaFormat out);
/**
* Called when the source is enabled.
*/
void enable();
/**
* Called when the source is disabled.
*
* @param queue A representation of the currently buffered {@link MediaChunk}s.
*/
void disable(List<MediaChunk> queue);
/**
* Indicates to the source that it should still be checking for updates to the stream.
*
* @param playbackPositionUs The current playback position.
*/
void continueBuffering(long playbackPositionUs);
/**
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
* be performed by the calling {@link ChunkSampleSource}.
* <p>
* The next operation comprises of a possibly shortened queue length (shortened if the
* implementation wishes for the caller to discard {@link MediaChunk}s from the queue), together
* with the next {@link Chunk} to load. The next chunk may be a {@link MediaChunk} to be added to
* the queue, or another {@link Chunk} type (e.g. to load initialization data), or null if the
* source is not able to provide a chunk in its current state.
*
* @param queue A representation of the currently buffered {@link MediaChunk}s.
* @param seekPositionUs If the queue is empty, this parameter must specify the seek position. If
* the queue is non-empty then this parameter is ignored.
* @param playbackPositionUs The current playback position.
* @param out A holder for the next operation, whose {@link ChunkOperationHolder#queueSize} is
* initially equal to the length of the queue, and whose {@link ChunkOperationHolder#chunk} is
* initially equal to null or a {@link Chunk} previously supplied by the {@link ChunkSource}
* that the caller has not yet finished loading. In the latter case the chunk can either be
* replaced or left unchanged. Note that leaving the chunk unchanged is both preferred and
* more efficient than replacing it with a new but identical chunk.
*/
void getChunkOperation(List<? extends MediaChunk> queue, long seekPositionUs,
long playbackPositionUs, ChunkOperationHolder out);
/**
* If the {@link ChunkSource} is currently unable to provide chunks through
* {@link ChunkSource#getChunkOperation}, then this method returns the underlying cause. Returns
* null otherwise.
*
* @return An {@link IOException}, or null.
*/
IOException getError();
}
/*
* Copyright (C) 2014 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.exoplayer.chunk;
import java.util.Comparator;
/**
* A format definition for streams.
*/
public final class Format {
/**
* Sorts {@link Format} objects in order of decreasing bandwidth.
*/
public static final class DecreasingBandwidthComparator implements Comparator<Format> {
@Override
public int compare(Format a, Format b) {
return b.bandwidth - a.bandwidth;
}
}
/**
* An identifier for the format.
*/
public final int id;
/**
* The mime type of the format.
*/
public final String mimeType;
/**
* The width of the video in pixels, or -1 for non-video formats.
*/
public final int width;
/**
* The height of the video in pixels, or -1 for non-video formats.
*/
public final int height;
/**
* The number of audio channels, or -1 for non-audio formats.
*/
public final int numChannels;
/**
* The audio sampling rate in Hz, or -1 for non-audio formats.
*/
public final int audioSamplingRate;
/**
* The average bandwidth in bytes per second.
*/
public final int bandwidth;
/**
* @param id The format identifier.
* @param mimeType The format mime type.
* @param width The width of the video in pixels, or -1 for non-video formats.
* @param height The height of the video in pixels, or -1 for non-video formats.
* @param numChannels The number of audio channels, or -1 for non-audio formats.
* @param audioSamplingRate The audio sampling rate in Hz, or -1 for non-audio formats.
* @param bandwidth The average bandwidth of the format in bytes per second.
*/
public Format(int id, String mimeType, int width, int height, int numChannels,
int audioSamplingRate, int bandwidth) {
this.id = id;
this.mimeType = mimeType;
this.width = width;
this.height = height;
this.numChannels = numChannels;
this.audioSamplingRate = audioSamplingRate;
this.bandwidth = bandwidth;
}
}
/*
* Copyright (C) 2014 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.exoplayer.chunk;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import java.util.Map;
import java.util.UUID;
/**
* An abstract base class for {@link Chunk}s that contain media samples.
*/
public abstract class MediaChunk extends Chunk {
/**
* The start time of the media contained by the chunk.
*/
public final long startTimeUs;
/**
* The end time of the media contained by the chunk.
*/
public final long endTimeUs;
/**
* The index of the next media chunk, or -1 if this is the last media chunk in the stream.
*/
public final int nextChunkIndex;
/**
* Constructor for a chunk of media samples.
*
* @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded.
* @param format The format of the stream to which this chunk belongs.
* @param trigger The reason for this chunk being selected.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
*/
public MediaChunk(DataSource dataSource, DataSpec dataSpec, Format format, int trigger,
long startTimeUs, long endTimeUs, int nextChunkIndex) {
super(dataSource, dataSpec, format, trigger);
this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs;
this.nextChunkIndex = nextChunkIndex;
}
/**
* Whether this is the last chunk in the stream.
*
* @return True if this is the last chunk in the stream. False otherwise.
*/
public final boolean isLastChunk() {
return nextChunkIndex == -1;
}
/**
* Seeks to the beginning of the chunk.
*/
public final void seekToStart() {
seekTo(startTimeUs, false);
}
/**
* Seeks to the specified position within the chunk.
*
* @param positionUs The desired seek time in microseconds.
* @param allowNoop True if the seek is allowed to do nothing if the result is more accurate than
* seeking to a key frame. Always pass false if it is required that the next sample be a key
* frame.
* @return True if the seek results in a discontinuity in the sequence of samples returned by
* {@link #read(SampleHolder)}. False otherwise.
*/
public abstract boolean seekTo(long positionUs, boolean allowNoop);
/**
* Reads the next media sample from the chunk.
*
* @param holder A holder to store the read sample.
* @return True if a sample was read. False if more data is still required.
* @throws ParserException If an error occurs parsing the media data.
* @throws IllegalStateException If called before {@link #init}, or after {@link #release}
*/
public abstract boolean read(SampleHolder holder) throws ParserException;
/**
* Returns the media format of the samples contained within this chunk.
*
* @return The sample media format.
*/
public abstract MediaFormat getMediaFormat();
/**
* Returns the pssh information associated with the chunk.
*
* @return The pssh information.
*/
public abstract Map<UUID, byte[]> getPsshInfo();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment