Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
876fa41b
authored
Feb 06, 2015
by
ojw28
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #283 from google/dev
dev -> dev-webm-vp9-opus
parents
3338a09c
147bbe6d
Hide whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
914 additions
and
1468 deletions
demo/src/main/AndroidManifest.xml
demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
demo/src/main/java/com/google/android/exoplayer/demo/full/EventLogger.java → demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java → demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
demo/src/main/java/com/google/android/exoplayer/demo/full/SmoothStreamingTestMediaDrmCallback.java → demo/src/main/java/com/google/android/exoplayer/demo/SmoothStreamingTestMediaDrmCallback.java
demo/src/main/java/com/google/android/exoplayer/demo/full/WidevineTestMediaDrmCallback.java → demo/src/main/java/com/google/android/exoplayer/demo/WidevineTestMediaDrmCallback.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DashRendererBuilder.java → demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DebugTrackRenderer.java → demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DefaultRendererBuilder.java → demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java → demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java → demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/SmoothStreamingRendererBuilder.java → demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/UnsupportedDrmException.java → demo/src/main/java/com/google/android/exoplayer/demo/player/UnsupportedDrmException.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/DashRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/HlsRendererBuilder.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java
demo/src/main/res/layout/player_activity_full.xml → demo/src/main/res/layout/player_activity.xml
demo/src/main/res/layout/player_activity_simple.xml
library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/Extractor.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java
library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
library/src/main/java/com/google/android/exoplayer/mp4/Track.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java
library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
demo/src/main/AndroidManifest.xml
View file @
876fa41b
...
...
@@ -42,12 +42,7 @@
</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"
<activity
android:name=
"com.google.android.exoplayer.demo.PlayerActivity"
android:configChanges=
"keyboardHidden|orientation|screenSize"
android:label=
"@string/application_name"
android:theme=
"@style/PlayerTheme"
/>
...
...
demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
View file @
876fa41b
...
...
@@ -44,16 +44,11 @@ public class DemoUtil {
public
static
final
UUID
WIDEVINE_UUID
=
new
UUID
(
0xEDEF8BA979D64ACE
L
,
0xA3C827DCD51D21ED
L
);
public
static
final
String
CONTENT_TYPE_EXTRA
=
"content_type"
;
public
static
final
String
CONTENT_ID_EXTRA
=
"content_id"
;
public
static
final
int
TYPE_DASH
=
0
;
public
static
final
int
TYPE_SS
=
1
;
public
static
final
int
TYPE_OTHER
=
2
;
public
static
final
int
TYPE_HLS
=
3
;
public
static
final
boolean
EXPOSE_EXPERIMENTAL_FEATURES
=
false
;
private
static
final
CookieManager
defaultCookieManager
;
static
{
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
EventLogger.java
→
demo/src/main/java/com/google/android/exoplayer/demo/EventLogger.java
View file @
876fa41b
...
...
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
;
package
com
.
google
.
android
.
exoplayer
.
demo
;
import
com.google.android.exoplayer.ExoPlayer
;
import
com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException
;
import
com.google.android.exoplayer.audio.AudioTrack
;
import
com.google.android.exoplayer.demo.
full.
player.DemoPlayer
;
import
com.google.android.exoplayer.demo.player.DemoPlayer
;
import
com.google.android.exoplayer.util.VerboseLogUtil
;
import
android.media.MediaCodec.CryptoException
;
...
...
@@ -63,8 +63,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override
public
void
onStateChanged
(
boolean
playWhenReady
,
int
state
)
{
Log
.
d
(
TAG
,
"state ["
+
getSessionTimeString
()
+
", "
+
playWhenReady
+
", "
+
getStateString
(
state
)
+
"]"
);
Log
.
d
(
TAG
,
"state ["
+
getSessionTimeString
()
+
", "
+
playWhenReady
+
", "
+
getStateString
(
state
)
+
"]"
);
}
@Override
...
...
@@ -81,8 +81,8 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
@Override
public
void
onBandwidthSample
(
int
elapsedMs
,
long
bytes
,
long
bitrateEstimate
)
{
Log
.
d
(
TAG
,
"bandwidth ["
+
getSessionTimeString
()
+
", "
+
bytes
+
", "
+
getTimeString
(
elapsedMs
)
+
", "
+
bitrateEstimate
+
"]"
);
Log
.
d
(
TAG
,
"bandwidth ["
+
getSessionTimeString
()
+
", "
+
bytes
+
", "
+
getTimeString
(
elapsedMs
)
+
", "
+
bitrateEstimate
+
"]"
);
}
@Override
...
...
@@ -104,21 +104,21 @@ public class EventLogger implements DemoPlayer.Listener, DemoPlayer.InfoListener
public
void
onLoadCompleted
(
int
sourceId
,
long
bytesLoaded
)
{
if
(
VerboseLogUtil
.
isTagEnabled
(
TAG
))
{
long
downloadTime
=
SystemClock
.
elapsedRealtime
()
-
loadStartTimeMs
[
sourceId
];
Log
.
v
(
TAG
,
"loadEnd ["
+
getSessionTimeString
()
+
", "
+
sourceId
+
", "
+
downloadTime
+
"]"
);
Log
.
v
(
TAG
,
"loadEnd ["
+
getSessionTimeString
()
+
", "
+
sourceId
+
", "
+
downloadTime
+
"]"
);
}
}
@Override
public
void
onVideoFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
Log
.
d
(
TAG
,
"videoFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Integer
.
toString
(
trigger
)
+
"]"
);
Log
.
d
(
TAG
,
"videoFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Integer
.
toString
(
trigger
)
+
"]"
);
}
@Override
public
void
onAudioFormatEnabled
(
String
formatId
,
int
trigger
,
int
mediaTimeMs
)
{
Log
.
d
(
TAG
,
"audioFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Integer
.
toString
(
trigger
)
+
"]"
);
Log
.
d
(
TAG
,
"audioFormat ["
+
getSessionTimeString
()
+
", "
+
formatId
+
", "
+
Integer
.
toString
(
trigger
)
+
"]"
);
}
// DemoPlayer.InternalErrorListener
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/Full
PlayerActivity.java
→
demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
View file @
876fa41b
...
...
@@ -13,21 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
;
package
com
.
google
.
android
.
exoplayer
.
demo
;
import
com.google.android.exoplayer.ExoPlayer
;
import
com.google.android.exoplayer.VideoSurfaceView
;
import
com.google.android.exoplayer.audio.AudioCapabilities
;
import
com.google.android.exoplayer.audio.AudioCapabilitiesReceiver
;
import
com.google.android.exoplayer.demo.DemoUtil
;
import
com.google.android.exoplayer.demo.R
;
import
com.google.android.exoplayer.demo.full.player.DashRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.DefaultRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.HlsRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.UnsupportedDrmException
;
import
com.google.android.exoplayer.demo.player.DashRendererBuilder
;
import
com.google.android.exoplayer.demo.player.DefaultRendererBuilder
;
import
com.google.android.exoplayer.demo.player.DemoPlayer
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.player.HlsRendererBuilder
;
import
com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder
;
import
com.google.android.exoplayer.demo.player.UnsupportedDrmException
;
import
com.google.android.exoplayer.metadata.TxxxMetadata
;
import
com.google.android.exoplayer.text.CaptionStyleCompat
;
import
com.google.android.exoplayer.text.SubtitleView
;
...
...
@@ -65,11 +63,14 @@ import java.util.Map;
/**
* An activity that plays media using {@link DemoPlayer}.
*/
public
class
Full
PlayerActivity
extends
Activity
implements
SurfaceHolder
.
Callback
,
OnClickListener
,
public
class
PlayerActivity
extends
Activity
implements
SurfaceHolder
.
Callback
,
OnClickListener
,
DemoPlayer
.
Listener
,
DemoPlayer
.
TextListener
,
DemoPlayer
.
Id3MetadataListener
,
AudioCapabilitiesReceiver
.
Listener
{
private
static
final
String
TAG
=
"FullPlayerActivity"
;
public
static
final
String
CONTENT_TYPE_EXTRA
=
"content_type"
;
public
static
final
String
CONTENT_ID_EXTRA
=
"content_id"
;
private
static
final
String
TAG
=
"PlayerActivity"
;
private
static
final
float
CAPTION_LINE_HEIGHT_RATIO
=
0.0533f
;
private
static
final
int
MENU_GROUP_TRACKS
=
1
;
...
...
@@ -110,10 +111,10 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
Intent
intent
=
getIntent
();
contentUri
=
intent
.
getData
();
contentType
=
intent
.
getIntExtra
(
DemoUtil
.
CONTENT_TYPE_EXTRA
,
DemoUtil
.
TYPE_OTHER
);
contentId
=
intent
.
getStringExtra
(
DemoUtil
.
CONTENT_ID_EXTRA
);
contentType
=
intent
.
getIntExtra
(
CONTENT_TYPE_EXTRA
,
DemoUtil
.
TYPE_OTHER
);
contentId
=
intent
.
getStringExtra
(
CONTENT_ID_EXTRA
);
setContentView
(
R
.
layout
.
player_activity
_full
);
setContentView
(
R
.
layout
.
player_activity
);
View
root
=
findViewById
(
R
.
id
.
root
);
root
.
setOnTouchListener
(
new
OnTouchListener
()
{
@Override
...
...
demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java
View file @
876fa41b
...
...
@@ -15,15 +15,17 @@
*/
package
com
.
google
.
android
.
exoplayer
.
demo
;
import
com.google.android.exoplayer.MediaCodecUtil
;
import
com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException
;
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.MimeTypes
;
import
android.app.Activity
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.net.Uri
;
import
android.os.Bundle
;
import
android.util.Log
;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.ViewGroup
;
...
...
@@ -38,6 +40,8 @@ import android.widget.TextView;
*/
public
class
SampleChooserActivity
extends
Activity
{
private
static
final
String
TAG
=
"SampleChooserActivity"
;
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
...
...
@@ -46,21 +50,25 @@ public class SampleChooserActivity extends 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 GTS DASH"
));
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
);
sampleAdapter
.
add
(
new
Header
(
"HLS"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
HLS
);
if
(
DemoUtil
.
EXPOSE_EXPERIMENTAL_FEATURES
)
{
sampleAdapter
.
add
(
new
Header
(
"YouTube WebM DASH (Experimental)"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
YOUTUBE_DASH_WEBM
);
sampleAdapter
.
add
(
new
Header
(
"Misc"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
MISC
);
// Add WebM samples if the device has a VP9 decoder.
try
{
if
(
MediaCodecUtil
.
getDecoderInfo
(
MimeTypes
.
VIDEO_VP9
,
false
)
!=
null
)
{
sampleAdapter
.
add
(
new
Header
(
"YouTube WebM DASH (Experimental)"
));
sampleAdapter
.
addAll
((
Object
[])
Samples
.
YOUTUBE_DASH_WEBM
);
}
}
catch
(
DecoderQueryException
e
)
{
Log
.
e
(
TAG
,
"Failed to query vp9 decoder"
,
e
);
}
sampleList
.
setAdapter
(
sampleAdapter
);
...
...
@@ -76,12 +84,10 @@ public class SampleChooserActivity extends Activity {
}
private
void
onSampleSelected
(
Sample
sample
)
{
Class
<?>
playerActivityClass
=
sample
.
fullPlayer
?
FullPlayerActivity
.
class
:
SimplePlayerActivity
.
class
;
Intent
mpdIntent
=
new
Intent
(
this
,
playerActivityClass
)
Intent
mpdIntent
=
new
Intent
(
this
,
PlayerActivity
.
class
)
.
setData
(
Uri
.
parse
(
sample
.
uri
))
.
putExtra
(
DemoUtil
.
CONTENT_ID_EXTRA
,
sample
.
contentId
)
.
putExtra
(
DemoUtil
.
CONTENT_TYPE_EXTRA
,
sample
.
type
);
.
putExtra
(
PlayerActivity
.
CONTENT_ID_EXTRA
,
sample
.
contentId
)
.
putExtra
(
PlayerActivity
.
CONTENT_TYPE_EXTRA
,
sample
.
type
);
startActivity
(
mpdIntent
);
}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
View file @
876fa41b
...
...
@@ -15,6 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer
.
demo
;
import
java.util.Locale
;
/**
* Holds statically defined sample definitions.
*/
...
...
@@ -26,72 +28,53 @@ package com.google.android.exoplayer.demo;
public
final
String
contentId
;
public
final
String
uri
;
public
final
int
type
;
public
final
boolean
fullPlayer
;
public
Sample
(
String
name
,
String
contentId
,
String
uri
,
int
type
,
boolean
fullPlayer
)
{
public
Sample
(
String
name
,
String
uri
,
int
type
)
{
this
(
name
,
name
.
toLowerCase
(
Locale
.
US
).
replaceAll
(
"\\s"
,
""
),
uri
,
type
);
}
public
Sample
(
String
name
,
String
contentId
,
String
uri
,
int
type
)
{
this
.
name
=
name
;
this
.
contentId
=
contentId
;
this
.
uri
=
uri
;
this
.
type
=
type
;
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
,
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
,
false
),
new
Sample
(
"Super speed (SmoothStreaming)"
,
"uid:ss:superspeed"
,
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism"
,
DemoUtil
.
TYPE_SS
,
false
),
new
Sample
(
"Apple master playlist (HLS)"
,
"uid:hls:applemaster"
,
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
+
"bipbop_4x3_variant.m3u8"
,
DemoUtil
.
TYPE_HLS
,
false
),
new
Sample
(
"Dizzy (Misc)"
,
"uid:misc:dizzy"
,
"http://html5demos.com/assets/dizzy.mp4"
,
DemoUtil
.
TYPE_OTHER
,
false
),
};
public
static
final
Sample
[]
YOUTUBE_DASH_MP4
=
new
Sample
[]
{
new
Sample
(
"Google Glass"
,
"bf5bb2419360daf1"
,
new
Sample
(
"Google Glass"
,
"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
,
true
),
new
Sample
(
"Google Play"
,
"3aa39fa2cc27967f"
,
+
"2D6A28B21F921D0B245CDCF36F7EB54A2B5ABFC2&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
new
Sample
(
"Google Play"
,
"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
,
true
),
+
"5B445837F55A40E0F28AACAA047982E372D177E2&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
};
public
static
final
Sample
[]
YOUTUBE_DASH_WEBM
=
new
Sample
[]
{
new
Sample
(
"Google Glass"
,
"bf5bb2419360daf1"
,
new
Sample
(
"Google Glass"
,
"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
,
true
),
new
Sample
(
"Google Play"
,
"3aa39fa2cc27967f"
,
+
"446E9C38E47E3EDAF39E0163C390FF83A7944918&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
new
Sample
(
"Google Play"
,
"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
,
true
),
+
"C0358CE1E335417D9A8D88FF192F0D5D8F6DA1B6&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
};
public
static
final
Sample
[]
SMOOTHSTREAMING
=
new
Sample
[]
{
new
Sample
(
"Super speed"
,
"uid:ss:superspeed"
,
new
Sample
(
"Super speed"
,
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism"
,
DemoUtil
.
TYPE_SS
,
true
),
new
Sample
(
"Super speed (PlayReady)"
,
"uid:ss:pr:superspeed"
,
DemoUtil
.
TYPE_SS
),
new
Sample
(
"Super speed (PlayReady)"
,
"http://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism"
,
DemoUtil
.
TYPE_SS
,
true
),
DemoUtil
.
TYPE_SS
),
};
public
static
final
Sample
[]
WIDEVINE_GTS
=
new
Sample
[]
{
...
...
@@ -99,54 +82,54 @@ package com.google.android.exoplayer.demo;
"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
,
true
),
+
"0BB075C396FFDDC97E526E8F77DC26FF9667D0D6&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
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
,
true
),
+
"5B41E2EB411F797097A359D6671D2CDE26272373&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
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
,
true
),
+
"B09589A34FA23527EFC1552907754BB8033870BD&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
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
,
true
),
+
"A1CA51EC40A1C1039BA800C41500DD448C03EEDA&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
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
,
true
),
+
"2FDF4BFA502FB5865C5C86401314BDDEA4799BD0&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
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
,
true
),
+
"22727BB612D24AA4FACE4EF62726F9461A9BF57A&key=ik0"
,
DemoUtil
.
TYPE_DASH
),
};
public
static
final
Sample
[]
HLS
=
new
Sample
[]
{
new
Sample
(
"Apple master playlist"
,
"uid:hls:applemaster"
,
new
Sample
(
"Apple master playlist"
,
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/"
+
"bipbop_4x3_variant.m3u8"
,
DemoUtil
.
TYPE_HLS
,
true
),
new
Sample
(
"Apple master playlist advanced"
,
"uid:hls:applemasteradvanced"
,
+
"bipbop_4x3_variant.m3u8"
,
DemoUtil
.
TYPE_HLS
),
new
Sample
(
"Apple master playlist advanced"
,
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/"
+
"bipbop_16x9_variant.m3u8"
,
DemoUtil
.
TYPE_HLS
,
true
),
new
Sample
(
"Apple single media playlist"
,
"uid:hls:applesinglemedia"
,
+
"bipbop_16x9_variant.m3u8"
,
DemoUtil
.
TYPE_HLS
),
new
Sample
(
"Apple single media playlist"
,
"https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/"
+
"prog_index.m3u8"
,
DemoUtil
.
TYPE_HLS
,
true
),
+
"prog_index.m3u8"
,
DemoUtil
.
TYPE_HLS
),
};
public
static
final
Sample
[]
MISC
=
new
Sample
[]
{
new
Sample
(
"Dizzy"
,
"
uid:misc:dizzy"
,
"
http://html5demos.com/assets/dizzy.mp4"
,
DemoUtil
.
TYPE_OTHER
,
true
),
new
Sample
(
"Dizzy (https->http redirect)"
,
"
uid:misc:dizzy2"
,
"
https://goo.gl/MtUDEj"
,
DemoUtil
.
TYPE_OTHER
,
true
),
new
Sample
(
"Apple AAC 10s"
,
"
uid:misc:appleaacseg"
,
"
https://devimages.apple.com.edgekey.net/"
new
Sample
(
"Dizzy"
,
"http://html5demos.com/assets/dizzy.mp4"
,
DemoUtil
.
TYPE_OTHER
),
new
Sample
(
"Dizzy (https->http redirect)"
,
"https://goo.gl/MtUDEj"
,
DemoUtil
.
TYPE_OTHER
),
new
Sample
(
"Apple AAC 10s"
,
"https://devimages.apple.com.edgekey.net/"
+
"streaming/examples/bipbop_4x3/gear0/fileSequence0.aac"
,
DemoUtil
.
TYPE_OTHER
,
true
),
DemoUtil
.
TYPE_OTHER
),
};
private
Samples
()
{}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
SmoothStreamingTestMediaDrmCallback.java
→
demo/src/main/java/com/google/android/exoplayer/demo/SmoothStreamingTestMediaDrmCallback.java
View file @
876fa41b
...
...
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
;
package
com
.
google
.
android
.
exoplayer
.
demo
;
import
com.google.android.exoplayer.demo.DemoUtil
;
import
com.google.android.exoplayer.drm.MediaDrmCallback
;
import
com.google.android.exoplayer.drm.StreamingDrmSessionManager
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
WidevineTestMediaDrmCallback.java
→
demo/src/main/java/com/google/android/exoplayer/demo/WidevineTestMediaDrmCallback.java
View file @
876fa41b
...
...
@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
;
package
com
.
google
.
android
.
exoplayer
.
demo
;
import
com.google.android.exoplayer.demo.DemoUtil
;
import
com.google.android.exoplayer.drm.MediaDrmCallback
;
import
android.annotation.TargetApi
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
player/DashRendererBuilder.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java
View file @
876fa41b
...
...
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
package
com
.
google
.
android
.
exoplayer
.
demo
.
player
;
import
com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer
;
import
com.google.android.exoplayer.DefaultLoadControl
;
...
...
@@ -38,8 +38,8 @@ import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
import
com.google.android.exoplayer.dash.mpd.Period
;
import
com.google.android.exoplayer.dash.mpd.Representation
;
import
com.google.android.exoplayer.demo.DemoUtil
;
import
com.google.android.exoplayer.demo.
full.
player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.
full.
player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.drm.DrmSessionManager
;
import
com.google.android.exoplayer.drm.MediaDrmCallback
;
import
com.google.android.exoplayer.drm.StreamingDrmSessionManager
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
player/DebugTrackRenderer.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/DebugTrackRenderer.java
View file @
876fa41b
...
...
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
package
com
.
google
.
android
.
exoplayer
.
demo
.
player
;
import
com.google.android.exoplayer.ExoPlaybackException
;
import
com.google.android.exoplayer.MediaCodecTrackRenderer
;
...
...
@@ -82,8 +82,8 @@ import android.widget.TextView;
}
private
String
getRenderString
()
{
return
"ms("
+
(
currentPositionUs
/
1000
)
+
"), "
+
getQualityString
()
+
", "
+
renderer
.
codecCounters
.
getDebugString
();
return
"ms("
+
(
currentPositionUs
/
1000
)
+
"), "
+
getQualityString
()
+
", "
+
renderer
.
codecCounters
.
getDebugString
();
}
private
String
getQualityString
()
{
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
player/DefaultRendererBuilder.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/DefaultRendererBuilder.java
View file @
876fa41b
...
...
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
package
com
.
google
.
android
.
exoplayer
.
demo
.
player
;
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
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.source.DefaultSampleSource
;
import
com.google.android.exoplayer.source.FrameworkSampleExtractor
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
player/DemoPlayer.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/DemoPlayer.java
View file @
876fa41b
...
...
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
package
com
.
google
.
android
.
exoplayer
.
demo
.
player
;
import
com.google.android.exoplayer.Ac3PassthroughAudioTrackRenderer
;
import
com.google.android.exoplayer.DummyTrackRenderer
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
player/HlsRendererBuilder.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/HlsRendererBuilder.java
View file @
876fa41b
...
...
@@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
package
com
.
google
.
android
.
exoplayer
.
demo
.
player
;
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
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.hls.HlsChunkSource
;
import
com.google.android.exoplayer.hls.HlsPlaylist
;
import
com.google.android.exoplayer.hls.HlsPlaylistParser
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
player/SmoothStreamingRendererBuilder.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java
View file @
876fa41b
...
...
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
package
com
.
google
.
android
.
exoplayer
.
demo
.
player
;
import
com.google.android.exoplayer.DefaultLoadControl
;
import
com.google.android.exoplayer.LoadControl
;
...
...
@@ -27,8 +27,8 @@ 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.chunk.MultiTrackChunkSource
;
import
com.google.android.exoplayer.demo.
full.
player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.
full.
player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.drm.DrmSessionManager
;
import
com.google.android.exoplayer.drm.MediaDrmCallback
;
import
com.google.android.exoplayer.drm.StreamingDrmSessionManager
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/
full/
player/UnsupportedDrmException.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/UnsupportedDrmException.java
View file @
876fa41b
...
...
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
demo
.
full
.
player
;
package
com
.
google
.
android
.
exoplayer
.
demo
.
player
;
/**
* Exception thrown when the required level of DRM is not supported.
...
...
demo/src/main/java/com/google/android/exoplayer/demo/simple/DashRendererBuilder.java
deleted
100644 → 0
View file @
3338a09c
/*
* 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.MediaCodecUtil.DecoderQueryException
;
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.DashChunkSource
;
import
com.google.android.exoplayer.dash.mpd.AdaptationSet
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescription
;
import
com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser
;
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.UriDataSource
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.Util
;
import
android.media.MediaCodec
;
import
android.os.Handler
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.List
;
/**
* A {@link RendererBuilder} for DASH.
*/
/* package */
class
DashRendererBuilder
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
static
final
int
LIVE_EDGE_LATENCY_MS
=
30000
;
private
final
SimplePlayerActivity
playerActivity
;
private
final
String
userAgent
;
private
final
String
url
;
private
final
String
contentId
;
private
RendererBuilderCallback
callback
;
private
ManifestFetcher
<
MediaPresentationDescription
>
manifestFetcher
;
public
DashRendererBuilder
(
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
;
MediaPresentationDescriptionParser
parser
=
new
MediaPresentationDescriptionParser
();
manifestFetcher
=
new
ManifestFetcher
<
MediaPresentationDescription
>(
parser
,
contentId
,
url
,
userAgent
);
manifestFetcher
.
singleLoad
(
playerActivity
.
getMainLooper
(),
this
);
}
@Override
public
void
onManifestError
(
String
contentId
,
IOException
e
)
{
callback
.
onRenderersError
(
e
);
}
@Override
public
void
onManifest
(
String
contentId
,
MediaPresentationDescription
manifest
)
{
Period
period
=
manifest
.
periods
.
get
(
0
);
Handler
mainHandler
=
playerActivity
.
getMainHandler
();
LoadControl
loadControl
=
new
DefaultLoadControl
(
new
BufferPool
(
BUFFER_SEGMENT_SIZE
));
DefaultBandwidthMeter
bandwidthMeter
=
new
DefaultBandwidthMeter
();
// Determine which video representations we should use for playback.
int
maxDecodableFrameSize
;
try
{
maxDecodableFrameSize
=
MediaCodecUtil
.
maxH264DecodableFrameSize
();
}
catch
(
DecoderQueryException
e
)
{
callback
.
onRenderersError
(
e
);
return
;
}
int
videoAdaptationSetIndex
=
period
.
getAdaptationSetIndex
(
AdaptationSet
.
TYPE_VIDEO
);
List
<
Representation
>
videoRepresentations
=
period
.
adaptationSets
.
get
(
videoAdaptationSetIndex
).
representations
;
ArrayList
<
Integer
>
videoRepresentationIndexList
=
new
ArrayList
<
Integer
>();
for
(
int
i
=
0
;
i
<
videoRepresentations
.
size
();
i
++)
{
Format
format
=
videoRepresentations
.
get
(
i
).
format
;
if
(
format
.
width
*
format
.
height
>
maxDecodableFrameSize
)
{
// Filtering stream that device cannot play
}
else
if
(!
format
.
mimeType
.
equals
(
MimeTypes
.
VIDEO_MP4
)
&&
!
format
.
mimeType
.
equals
(
MimeTypes
.
VIDEO_WEBM
))
{
// Filtering unsupported mime type
}
else
{
videoRepresentationIndexList
.
add
(
i
);
}
}
// Build the video renderer.
final
MediaCodecVideoTrackRenderer
videoRenderer
;
if
(
videoRepresentationIndexList
.
isEmpty
())
{
videoRenderer
=
null
;
}
else
{
int
[]
videoRepresentationIndices
=
Util
.
toArray
(
videoRepresentationIndexList
);
DataSource
videoDataSource
=
new
UriDataSource
(
userAgent
,
bandwidthMeter
);
ChunkSource
videoChunkSource
=
new
DashChunkSource
(
manifestFetcher
,
videoAdaptationSetIndex
,
videoRepresentationIndices
,
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
),
LIVE_EDGE_LATENCY_MS
);
ChunkSampleSource
videoSampleSource
=
new
ChunkSampleSource
(
videoChunkSource
,
loadControl
,
VIDEO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
);
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
videoSampleSource
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
mainHandler
,
playerActivity
,
50
);
}
// Build the audio renderer.
int
audioAdaptationSetIndex
=
period
.
getAdaptationSetIndex
(
AdaptationSet
.
TYPE_AUDIO
);
DataSource
audioDataSource
=
new
UriDataSource
(
userAgent
,
bandwidthMeter
);
ChunkSource
audioChunkSource
=
new
DashChunkSource
(
manifestFetcher
,
audioAdaptationSetIndex
,
new
int
[]
{
0
},
audioDataSource
,
new
FormatEvaluator
.
FixedEvaluator
(),
LIVE_EDGE_LATENCY_MS
);
SampleSource
audioSampleSource
=
new
ChunkSampleSource
(
audioChunkSource
,
loadControl
,
AUDIO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
);
MediaCodecAudioTrackRenderer
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
audioSampleSource
);
callback
.
onRenderers
(
videoRenderer
,
audioRenderer
);
}
}
demo/src/main/java/com/google/android/exoplayer/demo/simple/DefaultRendererBuilder.java
deleted
100644 → 0
View file @
3338a09c
/*
* 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.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
com.google.android.exoplayer.source.DefaultSampleSource
;
import
com.google.android.exoplayer.source.FrameworkSampleExtractor
;
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.
DefaultSampleSource
sampleSource
=
new
DefaultSampleSource
(
new
FrameworkSampleExtractor
(
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
);
}
}
demo/src/main/java/com/google/android/exoplayer/demo/simple/HlsRendererBuilder.java
deleted
100644 → 0
View file @
3338a09c
/*
* 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.MediaCodecAudioTrackRenderer
;
import
com.google.android.exoplayer.MediaCodecVideoTrackRenderer
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer
;
import
com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilder
;
import
com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback
;
import
com.google.android.exoplayer.hls.HlsChunkSource
;
import
com.google.android.exoplayer.hls.HlsPlaylist
;
import
com.google.android.exoplayer.hls.HlsPlaylistParser
;
import
com.google.android.exoplayer.hls.HlsSampleSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DefaultBandwidthMeter
;
import
com.google.android.exoplayer.upstream.UriDataSource
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
;
import
android.media.MediaCodec
;
import
java.io.IOException
;
/**
* A {@link RendererBuilder} for HLS.
*/
/* package */
class
HlsRendererBuilder
implements
RendererBuilder
,
ManifestCallback
<
HlsPlaylist
>
{
private
final
SimplePlayerActivity
playerActivity
;
private
final
String
userAgent
;
private
final
String
url
;
private
final
String
contentId
;
private
RendererBuilderCallback
callback
;
public
HlsRendererBuilder
(
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
;
HlsPlaylistParser
parser
=
new
HlsPlaylistParser
();
ManifestFetcher
<
HlsPlaylist
>
playlistFetcher
=
new
ManifestFetcher
<
HlsPlaylist
>(
parser
,
contentId
,
url
,
userAgent
);
playlistFetcher
.
singleLoad
(
playerActivity
.
getMainLooper
(),
this
);
}
@Override
public
void
onManifestError
(
String
contentId
,
IOException
e
)
{
callback
.
onRenderersError
(
e
);
}
@Override
public
void
onManifest
(
String
contentId
,
HlsPlaylist
manifest
)
{
DefaultBandwidthMeter
bandwidthMeter
=
new
DefaultBandwidthMeter
();
DataSource
dataSource
=
new
UriDataSource
(
userAgent
,
bandwidthMeter
);
HlsChunkSource
chunkSource
=
new
HlsChunkSource
(
dataSource
,
url
,
manifest
,
bandwidthMeter
,
null
,
HlsChunkSource
.
ADAPTIVE_MODE_SPLICE
);
HlsSampleSource
sampleSource
=
new
HlsSampleSource
(
chunkSource
,
true
,
2
);
MediaCodecVideoTrackRenderer
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
sampleSource
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
playerActivity
.
getMainHandler
(),
playerActivity
,
50
);
MediaCodecAudioTrackRenderer
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
sampleSource
);
TrackRenderer
[]
renderers
=
new
TrackRenderer
[
DemoPlayer
.
RENDERER_COUNT
];
renderers
[
DemoPlayer
.
TYPE_VIDEO
]
=
videoRenderer
;
renderers
[
DemoPlayer
.
TYPE_AUDIO
]
=
audioRenderer
;
callback
.
onRenderers
(
videoRenderer
,
audioRenderer
);
}
}
demo/src/main/java/com/google/android/exoplayer/demo/simple/SimplePlayerActivity.java
deleted
100644 → 0
View file @
3338a09c
/*
* 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"
;
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
long
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
,
DemoUtil
.
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
);
DemoUtil
.
setDefaultCookieManager
();
}
@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
DemoUtil
.
TYPE_SS
:
return
new
SmoothStreamingRendererBuilder
(
this
,
userAgent
,
contentUri
.
toString
(),
contentId
);
case
DemoUtil
.
TYPE_DASH
:
return
new
DashRendererBuilder
(
this
,
userAgent
,
contentUri
.
toString
(),
contentId
);
case
DemoUtil
.
TYPE_HLS
:
return
new
HlsRendererBuilder
(
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
,
float
pixelWidthHeightRatio
)
{
surfaceView
.
setVideoWidthHeightRatio
(
height
==
0
?
1
:
(
pixelWidthHeightRatio
*
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
);
}
}
}
demo/src/main/java/com/google/android/exoplayer/demo/simple/SmoothStreamingRendererBuilder.java
deleted
100644 → 0
View file @
3338a09c
/*
* 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.MediaCodecUtil.DecoderQueryException
;
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.SmoothStreamingManifestParser
;
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.UriDataSource
;
import
com.google.android.exoplayer.util.ManifestFetcher
;
import
com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback
;
import
com.google.android.exoplayer.util.Util
;
import
android.media.MediaCodec
;
import
android.os.Handler
;
import
java.io.IOException
;
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
static
final
int
LIVE_EDGE_LATENCY_MS
=
30000
;
private
final
SimplePlayerActivity
playerActivity
;
private
final
String
userAgent
;
private
final
String
url
;
private
final
String
contentId
;
private
RendererBuilderCallback
callback
;
private
ManifestFetcher
<
SmoothStreamingManifest
>
manifestFetcher
;
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
;
SmoothStreamingManifestParser
parser
=
new
SmoothStreamingManifestParser
();
manifestFetcher
=
new
ManifestFetcher
<
SmoothStreamingManifest
>(
parser
,
contentId
,
url
+
"/Manifest"
,
userAgent
);
manifestFetcher
.
singleLoad
(
playerActivity
.
getMainLooper
(),
this
);
}
@Override
public
void
onManifestError
(
String
contentId
,
IOException
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
;
try
{
maxDecodableFrameSize
=
MediaCodecUtil
.
maxH264DecodableFrameSize
();
}
catch
(
DecoderQueryException
e
)
{
callback
.
onRenderersError
(
e
);
return
;
}
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
=
Util
.
toArray
(
videoTrackIndexList
);
// Build the video renderer.
DataSource
videoDataSource
=
new
UriDataSource
(
userAgent
,
bandwidthMeter
);
ChunkSource
videoChunkSource
=
new
SmoothStreamingChunkSource
(
manifestFetcher
,
videoStreamElementIndex
,
videoTrackIndices
,
videoDataSource
,
new
AdaptiveEvaluator
(
bandwidthMeter
),
LIVE_EDGE_LATENCY_MS
);
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
UriDataSource
(
userAgent
,
bandwidthMeter
);
ChunkSource
audioChunkSource
=
new
SmoothStreamingChunkSource
(
manifestFetcher
,
audioStreamElementIndex
,
new
int
[]
{
0
},
audioDataSource
,
new
FormatEvaluator
.
FixedEvaluator
(),
LIVE_EDGE_LATENCY_MS
);
SampleSource
audioSampleSource
=
new
ChunkSampleSource
(
audioChunkSource
,
loadControl
,
AUDIO_BUFFER_SEGMENTS
*
BUFFER_SEGMENT_SIZE
,
true
);
MediaCodecAudioTrackRenderer
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
audioSampleSource
);
callback
.
onRenderers
(
videoRenderer
,
audioRenderer
);
}
}
demo/src/main/res/layout/player_activity
_full
.xml
→
demo/src/main/res/layout/player_activity.xml
View file @
876fa41b
File moved
demo/src/main/res/layout/player_activity_simple.xml
deleted
100644 → 0
View file @
3338a09c
<?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>
library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java
View file @
876fa41b
...
...
@@ -375,10 +375,13 @@ import java.util.List;
}
private
void
updatePositionUs
()
{
positionUs
=
timeSourceTrackRenderer
!=
null
&&
enabledRenderers
.
contains
(
timeSourceTrackRenderer
)
?
timeSourceTrackRenderer
.
getCurrentPositionUs
()
:
mediaClock
.
getPositionUs
();
if
(
timeSourceTrackRenderer
!=
null
&&
enabledRenderers
.
contains
(
timeSourceTrackRenderer
)
&&
!
timeSourceTrackRenderer
.
isEnded
())
{
positionUs
=
timeSourceTrackRenderer
.
getCurrentPositionUs
();
mediaClock
.
setPositionUs
(
positionUs
);
}
else
{
positionUs
=
mediaClock
.
getPositionUs
();
}
elapsedRealtimeUs
=
SystemClock
.
elapsedRealtime
()
*
1000
;
}
...
...
library/src/main/java/com/google/android/exoplayer/chunk/parser/Extractor.java
View file @
876fa41b
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
.
parser
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.SampleHolder
;
...
...
@@ -79,6 +80,11 @@ public interface Extractor {
public
MediaFormat
getFormat
();
/**
* Returns the duration of the stream in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
*/
public
long
getDurationUs
();
/**
* Returns the pssh information parsed from the stream.
*
* @return The pssh information. May be null if pssh data has yet to be parsed, or if the stream
...
...
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
View file @
876fa41b
...
...
@@ -24,21 +24,18 @@ import com.google.android.exoplayer.chunk.parser.SegmentIndex;
import
com.google.android.exoplayer.mp4.Atom
;
import
com.google.android.exoplayer.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.mp4.Atom.LeafAtom
;
import
com.google.android.exoplayer.mp4.CommonMp4AtomParsers
;
import
com.google.android.exoplayer.mp4.Mp4Util
;
import
com.google.android.exoplayer.mp4.Track
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
import
android.annotation.SuppressLint
;
import
android.media.MediaCodec
;
import
android.media.MediaExtractor
;
import
android.util.Pair
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.HashMap
;
...
...
@@ -67,14 +64,8 @@ public final class FragmentedMp4Extractor implements Extractor {
private
static
final
int
READ_TERMINATING_RESULTS
=
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
|
RESULT_READ_SAMPLE
|
RESULT_NEED_SAMPLE_HOLDER
;
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
private
static
final
byte
[]
PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE
=
new
byte
[]
{-
94
,
57
,
79
,
82
,
90
,
-
101
,
79
,
20
,
-
94
,
68
,
108
,
66
,
124
,
100
,
-
115
,
-
12
};
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
private
static
final
int
[]
AC3_CHANNEL_COUNTS
=
new
int
[]
{
2
,
1
,
2
,
3
,
3
,
4
,
4
,
5
};
/** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
private
static
final
int
[]
AC3_BIT_RATES
=
new
int
[]
{
32
,
40
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
160
,
192
,
224
,
256
,
320
,
384
,
448
,
512
,
576
,
640
};
// Parser states
private
static
final
int
STATE_READING_ATOM_HEADER
=
0
;
...
...
@@ -82,10 +73,6 @@ public final class FragmentedMp4Extractor implements Extractor {
private
static
final
int
STATE_READING_ENCRYPTION_DATA
=
2
;
private
static
final
int
STATE_READING_SAMPLE
=
3
;
// Atom data offsets
private
static
final
int
ATOM_HEADER_SIZE
=
8
;
private
static
final
int
FULL_ATOM_HEADER_SIZE
=
12
;
// Atoms that the parser cares about
private
static
final
Set
<
Integer
>
PARSED_ATOMS
;
static
{
...
...
@@ -173,7 +160,7 @@ public final class FragmentedMp4Extractor implements Extractor {
public
FragmentedMp4Extractor
(
int
workaroundFlags
)
{
this
.
workaroundFlags
=
workaroundFlags
;
parserState
=
STATE_READING_ATOM_HEADER
;
atomHeader
=
new
ParsableByteArray
(
ATOM_HEADER_SIZE
);
atomHeader
=
new
ParsableByteArray
(
Mp4Util
.
ATOM_HEADER_SIZE
);
extendedTypeScratch
=
new
byte
[
16
];
containerAtoms
=
new
Stack
<
ContainerAtom
>();
fragmentRun
=
new
TrackFragment
();
...
...
@@ -211,6 +198,11 @@ public final class FragmentedMp4Extractor implements Extractor {
}
@Override
public
long
getDurationUs
()
{
return
track
==
null
?
C
.
UNKNOWN_TIME_US
:
track
.
durationUs
;
}
@Override
public
int
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
out
)
throws
ParserException
{
try
{
...
...
@@ -227,7 +219,7 @@ public final class FragmentedMp4Extractor implements Extractor {
results
|=
readEncryptionData
(
inputStream
);
break
;
default
:
results
|=
readOrSkipSample
(
inputStream
,
out
);
results
|=
readOrSkipSample
(
inputStream
,
out
);
break
;
}
}
...
...
@@ -276,14 +268,14 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private
int
readAtomHeader
(
NonBlockingInputStream
inputStream
)
{
int
remainingBytes
=
ATOM_HEADER_SIZE
-
atomBytesRead
;
int
remainingBytes
=
Mp4Util
.
ATOM_HEADER_SIZE
-
atomBytesRead
;
int
bytesRead
=
inputStream
.
read
(
atomHeader
.
data
,
atomBytesRead
,
remainingBytes
);
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_STREAM
;
}
rootAtomBytesRead
+=
bytesRead
;
atomBytesRead
+=
bytesRead
;
if
(
atomBytesRead
!=
ATOM_HEADER_SIZE
)
{
if
(
atomBytesRead
!=
Mp4Util
.
ATOM_HEADER_SIZE
)
{
return
RESULT_NEED_MORE_DATA
;
}
...
...
@@ -305,10 +297,10 @@ public final class FragmentedMp4Extractor implements Extractor {
if
(
CONTAINER_TYPES
.
contains
(
atomTypeInteger
))
{
enterState
(
STATE_READING_ATOM_HEADER
);
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
rootAtomBytesRead
+
atomSize
-
ATOM_HEADER_SIZE
));
rootAtomBytesRead
+
atomSize
-
Mp4Util
.
ATOM_HEADER_SIZE
));
}
else
{
atomData
=
new
ParsableByteArray
(
atomSize
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
ATOM_HEADER_SIZE
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
Mp4Util
.
ATOM_HEADER_SIZE
);
enterState
(
STATE_READING_ATOM_PAYLOAD
);
}
}
else
{
...
...
@@ -371,13 +363,13 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private
void
onMoovContainerAtomRead
(
ContainerAtom
moov
)
{
List
<
Atom
>
moovChildren
=
moov
.
c
hildren
;
List
<
Atom
.
LeafAtom
>
moovChildren
=
moov
.
leafC
hildren
;
int
moovChildrenSize
=
moovChildren
.
size
();
for
(
int
i
=
0
;
i
<
moovChildrenSize
;
i
++)
{
Atom
child
=
moovChildren
.
get
(
i
);
Leaf
Atom
child
=
moovChildren
.
get
(
i
);
if
(
child
.
type
==
Atom
.
TYPE_pssh
)
{
ParsableByteArray
psshAtom
=
((
LeafAtom
)
child
)
.
data
;
psshAtom
.
setPosition
(
FULL_ATOM_HEADER_SIZE
);
ParsableByteArray
psshAtom
=
child
.
data
;
psshAtom
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
);
UUID
uuid
=
new
UUID
(
psshAtom
.
readLong
(),
psshAtom
.
readLong
());
int
dataSize
=
psshAtom
.
readInt
();
byte
[]
data
=
new
byte
[
dataSize
];
...
...
@@ -387,7 +379,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
ContainerAtom
mvex
=
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_mvex
);
extendsDefaults
=
parseTrex
(
mvex
.
getLeafAtomOfType
(
Atom
.
TYPE_trex
).
data
);
track
=
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
));
track
=
CommonMp4AtomParsers
.
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
));
}
private
void
onMoofContainerAtomRead
(
ContainerAtom
moof
)
{
...
...
@@ -412,7 +404,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a trex atom (defined in 14496-12).
*/
private
static
DefaultSampleValues
parseTrex
(
ParsableByteArray
trex
)
{
trex
.
setPosition
(
FULL_ATOM_HEADER_SIZE
+
4
);
trex
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
+
4
);
int
defaultSampleDescriptionIndex
=
trex
.
readUnsignedIntToInt
()
-
1
;
int
defaultSampleDuration
=
trex
.
readUnsignedIntToInt
();
int
defaultSampleSize
=
trex
.
readUnsignedIntToInt
();
...
...
@@ -421,388 +413,6 @@ public final class FragmentedMp4Extractor implements Extractor {
defaultSampleSize
,
defaultSampleFlags
);
}
/**
* Parses a trak atom (defined in 14496-12).
*/
private
static
Track
parseTrak
(
ContainerAtom
trak
)
{
ContainerAtom
mdia
=
trak
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
);
int
trackType
=
parseHdlr
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_hdlr
).
data
);
Assertions
.
checkState
(
trackType
==
Track
.
TYPE_AUDIO
||
trackType
==
Track
.
TYPE_VIDEO
||
trackType
==
Track
.
TYPE_TEXT
);
Pair
<
Integer
,
Long
>
header
=
parseTkhd
(
trak
.
getLeafAtomOfType
(
Atom
.
TYPE_tkhd
).
data
);
int
id
=
header
.
first
;
// TODO: This value should be used to set a duration field on the Track object
// instantiated below, however we've found examples where the value is 0. Revisit whether we
// should set it anyway (and just have it be wrong for bad media streams).
// long duration = header.second;
long
timescale
=
parseMdhd
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_mdhd
).
data
);
ContainerAtom
stbl
=
mdia
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
)
.
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
Pair
<
MediaFormat
,
TrackEncryptionBox
[]>
sampleDescriptions
=
parseStsd
(
stbl
.
getLeafAtomOfType
(
Atom
.
TYPE_stsd
).
data
);
return
new
Track
(
id
,
trackType
,
timescale
,
sampleDescriptions
.
first
,
sampleDescriptions
.
second
);
}
/**
* Parses a tkhd atom (defined in 14496-12).
*
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
* the movie header box). The duration is set to -1 if the duration is unspecified.
*/
private
static
Pair
<
Integer
,
Long
>
parseTkhd
(
ParsableByteArray
tkhd
)
{
tkhd
.
setPosition
(
ATOM_HEADER_SIZE
);
int
fullAtom
=
tkhd
.
readInt
();
int
version
=
parseFullAtomVersion
(
fullAtom
);
tkhd
.
skip
(
version
==
0
?
8
:
16
);
int
trackId
=
tkhd
.
readInt
();
tkhd
.
skip
(
4
);
boolean
durationUnknown
=
true
;
int
durationPosition
=
tkhd
.
getPosition
();
int
durationByteCount
=
version
==
0
?
4
:
8
;
for
(
int
i
=
0
;
i
<
durationByteCount
;
i
++)
{
if
(
tkhd
.
data
[
durationPosition
+
i
]
!=
-
1
)
{
durationUnknown
=
false
;
break
;
}
}
long
duration
;
if
(
durationUnknown
)
{
tkhd
.
skip
(
durationByteCount
);
duration
=
-
1
;
}
else
{
duration
=
version
==
0
?
tkhd
.
readUnsignedInt
()
:
tkhd
.
readUnsignedLongToLong
();
}
return
Pair
.
create
(
trackId
,
duration
);
}
/**
* Parses an hdlr atom (defined in 14496-12).
*
* @param hdlr The hdlr atom to parse.
* @return The track type.
*/
private
static
int
parseHdlr
(
ParsableByteArray
hdlr
)
{
hdlr
.
setPosition
(
FULL_ATOM_HEADER_SIZE
+
4
);
return
hdlr
.
readInt
();
}
/**
* Parses an mdhd atom (defined in 14496-12).
*
* @param mdhd The mdhd atom to parse.
* @return The media timescale, defined as the number of time units that pass in one second.
*/
private
static
long
parseMdhd
(
ParsableByteArray
mdhd
)
{
mdhd
.
setPosition
(
ATOM_HEADER_SIZE
);
int
fullAtom
=
mdhd
.
readInt
();
int
version
=
parseFullAtomVersion
(
fullAtom
);
mdhd
.
skip
(
version
==
0
?
8
:
16
);
return
mdhd
.
readUnsignedInt
();
}
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
[]>
parseStsd
(
ParsableByteArray
stsd
)
{
stsd
.
setPosition
(
FULL_ATOM_HEADER_SIZE
);
int
numberOfEntries
=
stsd
.
readInt
();
MediaFormat
mediaFormat
=
null
;
TrackEncryptionBox
[]
trackEncryptionBoxes
=
new
TrackEncryptionBox
[
numberOfEntries
];
for
(
int
i
=
0
;
i
<
numberOfEntries
;
i
++)
{
int
childStartPosition
=
stsd
.
getPosition
();
int
childAtomSize
=
stsd
.
readInt
();
int
childAtomType
=
stsd
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_avc1
||
childAtomType
==
Atom
.
TYPE_avc3
||
childAtomType
==
Atom
.
TYPE_encv
)
{
Pair
<
MediaFormat
,
TrackEncryptionBox
>
avc
=
parseAvcFromParent
(
stsd
,
childStartPosition
,
childAtomSize
);
mediaFormat
=
avc
.
first
;
trackEncryptionBoxes
[
i
]
=
avc
.
second
;
}
else
if
(
childAtomType
==
Atom
.
TYPE_mp4a
||
childAtomType
==
Atom
.
TYPE_enca
||
childAtomType
==
Atom
.
TYPE_ac_3
)
{
Pair
<
MediaFormat
,
TrackEncryptionBox
>
audioSampleEntry
=
parseAudioSampleEntry
(
stsd
,
childAtomType
,
childStartPosition
,
childAtomSize
);
mediaFormat
=
audioSampleEntry
.
first
;
trackEncryptionBoxes
[
i
]
=
audioSampleEntry
.
second
;
}
else
if
(
childAtomType
==
Atom
.
TYPE_TTML
)
{
mediaFormat
=
MediaFormat
.
createTtmlFormat
();
}
stsd
.
setPosition
(
childStartPosition
+
childAtomSize
);
}
return
Pair
.
create
(
mediaFormat
,
trackEncryptionBoxes
);
}
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAvcFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
);
parent
.
skip
(
24
);
int
width
=
parent
.
readUnsignedShort
();
int
height
=
parent
.
readUnsignedShort
();
float
pixelWidthHeightRatio
=
1
;
parent
.
skip
(
50
);
List
<
byte
[]>
initializationData
=
null
;
TrackEncryptionBox
trackEncryptionBox
=
null
;
int
childPosition
=
parent
.
getPosition
();
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childStartPosition
=
parent
.
getPosition
();
int
childAtomSize
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_avcC
)
{
initializationData
=
parseAvcCFromParent
(
parent
,
childStartPosition
);
}
else
if
(
childAtomType
==
Atom
.
TYPE_sinf
)
{
trackEncryptionBox
=
parseSinfFromParent
(
parent
,
childStartPosition
,
childAtomSize
);
}
else
if
(
childAtomType
==
Atom
.
TYPE_pasp
)
{
pixelWidthHeightRatio
=
parsePaspFromParent
(
parent
,
childStartPosition
);
}
childPosition
+=
childAtomSize
;
}
MediaFormat
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
width
,
height
,
pixelWidthHeightRatio
,
initializationData
);
return
Pair
.
create
(
format
,
trackEncryptionBox
);
}
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAudioSampleEntry
(
ParsableByteArray
parent
,
int
atomType
,
int
position
,
int
size
)
{
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
);
parent
.
skip
(
16
);
int
channelCount
=
parent
.
readUnsignedShort
();
int
sampleSize
=
parent
.
readUnsignedShort
();
parent
.
skip
(
4
);
int
sampleRate
=
parent
.
readUnsignedFixedPoint1616
();
int
bitrate
=
MediaFormat
.
NO_VALUE
;
byte
[]
initializationData
=
null
;
TrackEncryptionBox
trackEncryptionBox
=
null
;
int
childPosition
=
parent
.
getPosition
();
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childStartPosition
=
parent
.
getPosition
();
int
childAtomSize
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_mp4a
||
atomType
==
Atom
.
TYPE_enca
)
{
if
(
childAtomType
==
Atom
.
TYPE_esds
)
{
initializationData
=
parseEsdsFromParent
(
parent
,
childStartPosition
);
// TODO: Do we really need to do this? See [Internal: b/10903778]
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
Pair
<
Integer
,
Integer
>
audioSpecificConfig
=
CodecSpecificDataUtil
.
parseAudioSpecificConfig
(
initializationData
);
sampleRate
=
audioSpecificConfig
.
first
;
channelCount
=
audioSpecificConfig
.
second
;
}
else
if
(
childAtomType
==
Atom
.
TYPE_sinf
)
{
trackEncryptionBox
=
parseSinfFromParent
(
parent
,
childStartPosition
,
childAtomSize
);
}
}
else
if
(
atomType
==
Atom
.
TYPE_ac_3
&&
childAtomType
==
Atom
.
TYPE_dac3
)
{
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
Ac3Format
ac3Format
=
parseAc3SpecificBoxFromParent
(
parent
,
childStartPosition
);
if
(
ac3Format
!=
null
)
{
sampleRate
=
ac3Format
.
sampleRate
;
channelCount
=
ac3Format
.
channelCount
;
bitrate
=
ac3Format
.
bitrate
;
}
// TODO: Add support for encrypted AC-3.
trackEncryptionBox
=
null
;
}
else
if
(
atomType
==
Atom
.
TYPE_ec_3
&&
childAtomType
==
Atom
.
TYPE_dec3
)
{
sampleRate
=
parseEc3SpecificBoxFromParent
(
parent
,
childStartPosition
);
trackEncryptionBox
=
null
;
}
childPosition
+=
childAtomSize
;
}
String
mimeType
;
if
(
atomType
==
Atom
.
TYPE_ac_3
)
{
mimeType
=
MimeTypes
.
AUDIO_AC3
;
}
else
if
(
atomType
==
Atom
.
TYPE_ec_3
)
{
mimeType
=
MimeTypes
.
AUDIO_EC3
;
}
else
{
mimeType
=
MimeTypes
.
AUDIO_AAC
;
}
MediaFormat
format
=
MediaFormat
.
createAudioFormat
(
mimeType
,
sampleSize
,
channelCount
,
sampleRate
,
bitrate
,
initializationData
==
null
?
null
:
Collections
.
singletonList
(
initializationData
));
return
Pair
.
create
(
format
,
trackEncryptionBox
);
}
private
static
Ac3Format
parseAc3SpecificBoxFromParent
(
ParsableByteArray
parent
,
int
position
)
{
// Start of the dac3 atom (defined in ETSI TS 102 366)
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
);
// fscod (sample rate code)
int
fscod
=
(
parent
.
readUnsignedByte
()
&
0xC0
)
>>
6
;
int
sampleRate
;
switch
(
fscod
)
{
case
0
:
sampleRate
=
48000
;
break
;
case
1
:
sampleRate
=
44100
;
break
;
case
2
:
sampleRate
=
32000
;
break
;
default
:
// TODO: The decoder should not use this stream.
return
null
;
}
int
nextByte
=
parent
.
readUnsignedByte
();
// Map acmod (audio coding mode) onto a channel count.
int
channelCount
=
AC3_CHANNEL_COUNTS
[(
nextByte
&
0x38
)
>>
3
];
// lfeon (low frequency effects on)
if
((
nextByte
&
0x04
)
!=
0
)
{
channelCount
++;
}
// Map bit_rate_code onto a bit-rate in kbit/s.
int
bitrate
=
AC3_BIT_RATES
[((
nextByte
&
0x03
)
<<
3
)
+
(
parent
.
readUnsignedByte
()
>>
5
)];
return
new
Ac3Format
(
channelCount
,
sampleRate
,
bitrate
);
}
private
static
int
parseEc3SpecificBoxFromParent
(
ParsableByteArray
parent
,
int
position
)
{
// Start of the dec3 atom (defined in ETSI TS 102 366)
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
);
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
return
0
;
}
private
static
List
<
byte
[]>
parseAvcCFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
+
4
);
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
int
nalUnitLength
=
(
parent
.
readUnsignedByte
()
&
0x3
)
+
1
;
if
(
nalUnitLength
!=
4
)
{
// readSample currently relies on a nalUnitLength of 4.
// TODO: Consider handling the case where it isn't.
throw
new
IllegalStateException
();
}
List
<
byte
[]>
initializationData
=
new
ArrayList
<
byte
[]>();
// TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
int
numSequenceParameterSets
=
parent
.
readUnsignedByte
()
&
0x1F
;
for
(
int
j
=
0
;
j
<
numSequenceParameterSets
;
j
++)
{
initializationData
.
add
(
parseChildNalUnit
(
parent
));
}
int
numPictureParamterSets
=
parent
.
readUnsignedByte
();
for
(
int
j
=
0
;
j
<
numPictureParamterSets
;
j
++)
{
initializationData
.
add
(
parseChildNalUnit
(
parent
));
}
return
initializationData
;
}
private
static
byte
[]
parseChildNalUnit
(
ParsableByteArray
atom
)
{
int
length
=
atom
.
readUnsignedShort
();
int
offset
=
atom
.
getPosition
();
atom
.
skip
(
length
);
return
CodecSpecificDataUtil
.
buildNalUnit
(
atom
.
data
,
offset
,
length
);
}
private
static
TrackEncryptionBox
parseSinfFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
int
childPosition
=
position
+
ATOM_HEADER_SIZE
;
TrackEncryptionBox
trackEncryptionBox
=
null
;
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childAtomSize
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_frma
)
{
parent
.
readInt
();
// dataFormat.
}
else
if
(
childAtomType
==
Atom
.
TYPE_schm
)
{
parent
.
skip
(
4
);
parent
.
readInt
();
// schemeType. Expect cenc
parent
.
readInt
();
// schemeVersion. Expect 0x00010000
}
else
if
(
childAtomType
==
Atom
.
TYPE_schi
)
{
trackEncryptionBox
=
parseSchiFromParent
(
parent
,
childPosition
,
childAtomSize
);
}
childPosition
+=
childAtomSize
;
}
return
trackEncryptionBox
;
}
private
static
float
parsePaspFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
);
int
hSpacing
=
parent
.
readUnsignedIntToInt
();
int
vSpacing
=
parent
.
readUnsignedIntToInt
();
return
(
float
)
hSpacing
/
vSpacing
;
}
private
static
TrackEncryptionBox
parseSchiFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
int
childPosition
=
position
+
ATOM_HEADER_SIZE
;
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childAtomSize
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_tenc
)
{
parent
.
skip
(
4
);
int
firstInt
=
parent
.
readInt
();
boolean
defaultIsEncrypted
=
(
firstInt
>>
8
)
==
1
;
int
defaultInitVectorSize
=
firstInt
&
0xFF
;
byte
[]
defaultKeyId
=
new
byte
[
16
];
parent
.
readBytes
(
defaultKeyId
,
0
,
defaultKeyId
.
length
);
return
new
TrackEncryptionBox
(
defaultIsEncrypted
,
defaultInitVectorSize
,
defaultKeyId
);
}
childPosition
+=
childAtomSize
;
}
return
null
;
}
private
static
byte
[]
parseEsdsFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
ATOM_HEADER_SIZE
+
4
);
// Start of the ES_Descriptor (defined in 14496-1)
parent
.
skip
(
1
);
// ES_Descriptor tag
int
varIntByte
=
parent
.
readUnsignedByte
();
while
(
varIntByte
>
127
)
{
varIntByte
=
parent
.
readUnsignedByte
();
}
parent
.
skip
(
2
);
// ES_ID
int
flags
=
parent
.
readUnsignedByte
();
if
((
flags
&
0x80
/* streamDependenceFlag */
)
!=
0
)
{
parent
.
skip
(
2
);
}
if
((
flags
&
0x40
/* URL_Flag */
)
!=
0
)
{
parent
.
skip
(
parent
.
readUnsignedShort
());
}
if
((
flags
&
0x20
/* OCRstreamFlag */
)
!=
0
)
{
parent
.
skip
(
2
);
}
// Start of the DecoderConfigDescriptor (defined in 14496-1)
parent
.
skip
(
1
);
// DecoderConfigDescriptor tag
varIntByte
=
parent
.
readUnsignedByte
();
while
(
varIntByte
>
127
)
{
varIntByte
=
parent
.
readUnsignedByte
();
}
parent
.
skip
(
13
);
// Start of AudioSpecificConfig (defined in 14496-3)
parent
.
skip
(
1
);
// AudioSpecificConfig tag
varIntByte
=
parent
.
readUnsignedByte
();
int
varInt
=
varIntByte
&
0x7F
;
while
(
varIntByte
>
127
)
{
varIntByte
=
parent
.
readUnsignedByte
();
varInt
=
varInt
<<
8
;
varInt
|=
varIntByte
&
0x7F
;
}
byte
[]
initializationData
=
new
byte
[
varInt
];
parent
.
readBytes
(
initializationData
,
0
,
varInt
);
return
initializationData
;
}
private
static
void
parseMoof
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
ContainerAtom
moof
,
TrackFragment
out
,
int
workaroundFlags
,
byte
[]
extendedTypeScratch
)
{
parseTraf
(
track
,
extendsDefaults
,
moof
.
getContainerAtomOfType
(
Atom
.
TYPE_traf
),
...
...
@@ -836,11 +446,11 @@ public final class FragmentedMp4Extractor implements Extractor {
parseSenc
(
senc
.
data
,
out
);
}
int
childrenSize
=
traf
.
c
hildren
.
size
();
int
childrenSize
=
traf
.
leafC
hildren
.
size
();
for
(
int
i
=
0
;
i
<
childrenSize
;
i
++)
{
Atom
atom
=
traf
.
c
hildren
.
get
(
i
);
LeafAtom
atom
=
traf
.
leafC
hildren
.
get
(
i
);
if
(
atom
.
type
==
Atom
.
TYPE_uuid
)
{
parseUuid
(
((
LeafAtom
)
atom
)
.
data
,
out
,
extendedTypeScratch
);
parseUuid
(
atom
.
data
,
out
,
extendedTypeScratch
);
}
}
}
...
...
@@ -848,9 +458,9 @@ public final class FragmentedMp4Extractor implements Extractor {
private
static
void
parseSaiz
(
TrackEncryptionBox
encryptionBox
,
ParsableByteArray
saiz
,
TrackFragment
out
)
{
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
saiz
.
setPosition
(
ATOM_HEADER_SIZE
);
saiz
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
saiz
.
readInt
();
int
flags
=
parseFullAtomFlags
(
fullAtom
);
int
flags
=
Mp4Util
.
parseFullAtomFlags
(
fullAtom
);
if
((
flags
&
0x01
)
==
1
)
{
saiz
.
skip
(
8
);
}
...
...
@@ -885,9 +495,9 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private
static
DefaultSampleValues
parseTfhd
(
DefaultSampleValues
extendsDefaults
,
ParsableByteArray
tfhd
)
{
tfhd
.
setPosition
(
ATOM_HEADER_SIZE
);
tfhd
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
tfhd
.
readInt
();
int
flags
=
parseFullAtomFlags
(
fullAtom
);
int
flags
=
Mp4Util
.
parseFullAtomFlags
(
fullAtom
);
tfhd
.
skip
(
4
);
// trackId
if
((
flags
&
0x01
/* base_data_offset_present */
)
!=
0
)
{
...
...
@@ -910,13 +520,13 @@ public final class FragmentedMp4Extractor implements Extractor {
/**
* Parses a tfdt atom (defined in 14496-12).
*
* @return baseMediaDecodeTime
.
The sum of the decode durations of all earlier samples in the
* @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the
* media, expressed in the media's timescale.
*/
private
static
long
parseTfdt
(
ParsableByteArray
tfdt
)
{
tfdt
.
setPosition
(
ATOM_HEADER_SIZE
);
tfdt
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
tfdt
.
readInt
();
int
version
=
parseFullAtomVersion
(
fullAtom
);
int
version
=
Mp4Util
.
parseFullAtomVersion
(
fullAtom
);
return
version
==
1
?
tfdt
.
readUnsignedLongToLong
()
:
tfdt
.
readUnsignedInt
();
}
...
...
@@ -931,9 +541,9 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private
static
void
parseTrun
(
Track
track
,
DefaultSampleValues
defaultSampleValues
,
long
decodeTime
,
int
workaroundFlags
,
ParsableByteArray
trun
,
TrackFragment
out
)
{
trun
.
setPosition
(
ATOM_HEADER_SIZE
);
trun
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
trun
.
readInt
();
int
flags
=
parseFullAtomFlags
(
fullAtom
);
int
flags
=
Mp4Util
.
parseFullAtomFlags
(
fullAtom
);
int
sampleCount
=
trun
.
readUnsignedIntToInt
();
if
((
flags
&
0x01
/* data_offset_present */
)
!=
0
)
{
...
...
@@ -991,7 +601,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private
static
void
parseUuid
(
ParsableByteArray
uuid
,
TrackFragment
out
,
byte
[]
extendedTypeScratch
)
{
uuid
.
setPosition
(
ATOM_HEADER_SIZE
);
uuid
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
uuid
.
readBytes
(
extendedTypeScratch
,
0
,
16
);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
...
...
@@ -1010,9 +620,9 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private
static
void
parseSenc
(
ParsableByteArray
senc
,
int
offset
,
TrackFragment
out
)
{
senc
.
setPosition
(
ATOM_HEADER_SIZE
+
offset
);
senc
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
+
offset
);
int
fullAtom
=
senc
.
readInt
();
int
flags
=
parseFullAtomFlags
(
fullAtom
);
int
flags
=
Mp4Util
.
parseFullAtomFlags
(
fullAtom
);
if
((
flags
&
0x01
/* override_track_encryption_box_parameters */
)
!=
0
)
{
// TODO: Implement this.
...
...
@@ -1034,9 +644,9 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a sidx atom (defined in 14496-12).
*/
private
static
SegmentIndex
parseSidx
(
ParsableByteArray
atom
)
{
atom
.
setPosition
(
ATOM_HEADER_SIZE
);
atom
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
atom
.
readInt
();
int
version
=
parseFullAtomVersion
(
fullAtom
);
int
version
=
Mp4Util
.
parseFullAtomVersion
(
fullAtom
);
atom
.
skip
(
4
);
long
timescale
=
atom
.
readUnsignedInt
();
...
...
@@ -1176,17 +786,8 @@ public final class FragmentedMp4Extractor implements Extractor {
inputStream
.
read
(
outputData
,
sampleSize
);
if
(
track
.
type
==
Track
.
TYPE_VIDEO
)
{
// The mp4 file contains length-prefixed NAL units, but the decoder wants start code
// delimited content. Replace length prefixes with start codes.
int
sampleOffset
=
outputData
.
position
()
-
sampleSize
;
int
position
=
sampleOffset
;
while
(
position
<
sampleOffset
+
sampleSize
)
{
outputData
.
position
(
position
);
int
length
=
readUnsignedIntToInt
(
outputData
);
outputData
.
position
(
position
);
outputData
.
put
(
NAL_START_CODE
);
position
+=
length
+
4
;
}
outputData
.
position
(
sampleOffset
+
sampleSize
);
// delimited content.
Mp4Util
.
replaceLengthPrefixesWithAvcStartCodes
(
outputData
,
sampleSize
);
}
out
.
size
=
sampleSize
;
}
...
...
@@ -1236,51 +837,4 @@ public final class FragmentedMp4Extractor implements Extractor {
}
}
/**
* Parses the version number out of the additional integer component of a full atom.
*/
private
static
int
parseFullAtomVersion
(
int
fullAtomInt
)
{
return
0x000000FF
&
(
fullAtomInt
>>
24
);
}
/**
* Parses the atom flags out of the additional integer component of a full atom.
*/
private
static
int
parseFullAtomFlags
(
int
fullAtomInt
)
{
return
0x00FFFFFF
&
fullAtomInt
;
}
/**
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
* assumed that the top bit will always be set to zero.
*
* @throws IllegalArgumentException If the top bit of the input data is set.
*/
private
static
int
readUnsignedIntToInt
(
ByteBuffer
data
)
{
int
result
=
0xFF
&
data
.
get
();
for
(
int
i
=
1
;
i
<
4
;
i
++)
{
result
<<=
8
;
result
|=
0xFF
&
data
.
get
();
}
if
(
result
<
0
)
{
throw
new
IllegalArgumentException
(
"Top bit not zero: "
+
result
);
}
return
result
;
}
/** Represents the format for AC-3 audio. */
private
static
final
class
Ac3Format
{
public
final
int
channelCount
;
public
final
int
sampleRate
;
public
final
int
bitrate
;
public
Ac3Format
(
int
channelCount
,
int
sampleRate
,
int
bitrate
)
{
this
.
channelCount
=
channelCount
;
this
.
sampleRate
=
sampleRate
;
this
.
bitrate
=
bitrate
;
}
}
}
library/src/main/java/com/google/android/exoplayer/chunk/parser/webm/WebmExtractor.java
View file @
876fa41b
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
.
parser
.
webm
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.SampleHolder
;
...
...
@@ -185,6 +186,11 @@ public final class WebmExtractor implements Extractor {
}
@Override
public
long
getDurationUs
()
{
return
durationUs
==
UNKNOWN
?
C
.
UNKNOWN_TIME_US
:
durationUs
;
}
@Override
public
Map
<
UUID
,
byte
[]>
getPsshInfo
()
{
// TODO: Parse pssh data from Webm streams.
return
null
;
...
...
library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
View file @
876fa41b
...
...
@@ -15,52 +15,64 @@
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
public
abstract
class
Atom
{
public
static
final
int
TYPE_avc1
=
0x61766331
;
public
static
final
int
TYPE_avc3
=
0x61766333
;
public
static
final
int
TYPE_esds
=
0x65736473
;
public
static
final
int
TYPE_mdat
=
0x6D646174
;
public
static
final
int
TYPE_mp4a
=
0x6D703461
;
public
static
final
int
TYPE_ac_3
=
0x61632D33
;
// ac-3
public
static
final
int
TYPE_dac3
=
0x64616333
;
public
static
final
int
TYPE_ec_3
=
0x65632D33
;
// ec-3
public
static
final
int
TYPE_dec3
=
0x64656333
;
public
static
final
int
TYPE_tfdt
=
0x74666474
;
public
static
final
int
TYPE_tfhd
=
0x74666864
;
public
static
final
int
TYPE_trex
=
0x74726578
;
public
static
final
int
TYPE_trun
=
0x7472756E
;
public
static
final
int
TYPE_sidx
=
0x73696478
;
public
static
final
int
TYPE_moov
=
0x6D6F6F76
;
public
static
final
int
TYPE_trak
=
0x7472616B
;
public
static
final
int
TYPE_mdia
=
0x6D646961
;
public
static
final
int
TYPE_minf
=
0x6D696E66
;
public
static
final
int
TYPE_stbl
=
0x7374626C
;
public
static
final
int
TYPE_avcC
=
0x61766343
;
public
static
final
int
TYPE_moof
=
0x6D6F6F66
;
public
static
final
int
TYPE_traf
=
0x74726166
;
public
static
final
int
TYPE_mvex
=
0x6D766578
;
public
static
final
int
TYPE_tkhd
=
0x746B6864
;
public
static
final
int
TYPE_mdhd
=
0x6D646864
;
public
static
final
int
TYPE_hdlr
=
0x68646C72
;
public
static
final
int
TYPE_stsd
=
0x73747364
;
public
static
final
int
TYPE_pssh
=
0x70737368
;
public
static
final
int
TYPE_sinf
=
0x73696E66
;
public
static
final
int
TYPE_schm
=
0x7363686D
;
public
static
final
int
TYPE_schi
=
0x73636869
;
public
static
final
int
TYPE_tenc
=
0x74656E63
;
public
static
final
int
TYPE_encv
=
0x656E6376
;
public
static
final
int
TYPE_enca
=
0x656E6361
;
public
static
final
int
TYPE_frma
=
0x66726D61
;
public
static
final
int
TYPE_saiz
=
0x7361697A
;
public
static
final
int
TYPE_uuid
=
0x75756964
;
public
static
final
int
TYPE_senc
=
0x73656E63
;
public
static
final
int
TYPE_pasp
=
0x70617370
;
public
static
final
int
TYPE_TTML
=
0x54544D4C
;
public
static
final
int
TYPE_avc1
=
getAtomTypeInteger
(
"avc1"
);
public
static
final
int
TYPE_avc3
=
getAtomTypeInteger
(
"avc3"
);
public
static
final
int
TYPE_esds
=
getAtomTypeInteger
(
"esds"
);
public
static
final
int
TYPE_mdat
=
getAtomTypeInteger
(
"mdat"
);
public
static
final
int
TYPE_mp4a
=
getAtomTypeInteger
(
"mp4a"
);
public
static
final
int
TYPE_ac_3
=
getAtomTypeInteger
(
"ac-3"
);
public
static
final
int
TYPE_dac3
=
getAtomTypeInteger
(
"dac3"
);
public
static
final
int
TYPE_ec_3
=
getAtomTypeInteger
(
"ec-3"
);
public
static
final
int
TYPE_dec3
=
getAtomTypeInteger
(
"dec3"
);
public
static
final
int
TYPE_tfdt
=
getAtomTypeInteger
(
"tfdt"
);
public
static
final
int
TYPE_tfhd
=
getAtomTypeInteger
(
"tfhd"
);
public
static
final
int
TYPE_trex
=
getAtomTypeInteger
(
"trex"
);
public
static
final
int
TYPE_trun
=
getAtomTypeInteger
(
"trun"
);
public
static
final
int
TYPE_sidx
=
getAtomTypeInteger
(
"sidx"
);
public
static
final
int
TYPE_moov
=
getAtomTypeInteger
(
"moov"
);
public
static
final
int
TYPE_trak
=
getAtomTypeInteger
(
"trak"
);
public
static
final
int
TYPE_mdia
=
getAtomTypeInteger
(
"mdia"
);
public
static
final
int
TYPE_minf
=
getAtomTypeInteger
(
"minf"
);
public
static
final
int
TYPE_stbl
=
getAtomTypeInteger
(
"stbl"
);
public
static
final
int
TYPE_avcC
=
getAtomTypeInteger
(
"avcC"
);
public
static
final
int
TYPE_moof
=
getAtomTypeInteger
(
"moof"
);
public
static
final
int
TYPE_traf
=
getAtomTypeInteger
(
"traf"
);
public
static
final
int
TYPE_mvex
=
getAtomTypeInteger
(
"mvex"
);
public
static
final
int
TYPE_tkhd
=
getAtomTypeInteger
(
"tkhd"
);
public
static
final
int
TYPE_mdhd
=
getAtomTypeInteger
(
"mdhd"
);
public
static
final
int
TYPE_hdlr
=
getAtomTypeInteger
(
"hdlr"
);
public
static
final
int
TYPE_stsd
=
getAtomTypeInteger
(
"stsd"
);
public
static
final
int
TYPE_pssh
=
getAtomTypeInteger
(
"pssh"
);
public
static
final
int
TYPE_sinf
=
getAtomTypeInteger
(
"sinf"
);
public
static
final
int
TYPE_schm
=
getAtomTypeInteger
(
"schm"
);
public
static
final
int
TYPE_schi
=
getAtomTypeInteger
(
"schi"
);
public
static
final
int
TYPE_tenc
=
getAtomTypeInteger
(
"tenc"
);
public
static
final
int
TYPE_encv
=
getAtomTypeInteger
(
"encv"
);
public
static
final
int
TYPE_enca
=
getAtomTypeInteger
(
"enca"
);
public
static
final
int
TYPE_frma
=
getAtomTypeInteger
(
"frma"
);
public
static
final
int
TYPE_saiz
=
getAtomTypeInteger
(
"saiz"
);
public
static
final
int
TYPE_uuid
=
getAtomTypeInteger
(
"uuid"
);
public
static
final
int
TYPE_senc
=
getAtomTypeInteger
(
"senc"
);
public
static
final
int
TYPE_pasp
=
getAtomTypeInteger
(
"pasp"
);
public
static
final
int
TYPE_TTML
=
getAtomTypeInteger
(
"TTML"
);
public
static
final
int
TYPE_vmhd
=
getAtomTypeInteger
(
"vmhd"
);
public
static
final
int
TYPE_smhd
=
getAtomTypeInteger
(
"smhd"
);
public
static
final
int
TYPE_mp4v
=
getAtomTypeInteger
(
"mp4v"
);
public
static
final
int
TYPE_stts
=
getAtomTypeInteger
(
"stts"
);
public
static
final
int
TYPE_stss
=
getAtomTypeInteger
(
"stss"
);
public
static
final
int
TYPE_stsc
=
getAtomTypeInteger
(
"stsc"
);
public
static
final
int
TYPE_stsz
=
getAtomTypeInteger
(
"stsz"
);
public
static
final
int
TYPE_stco
=
getAtomTypeInteger
(
"stco"
);
public
static
final
int
TYPE_co64
=
getAtomTypeInteger
(
"co64"
);
public
final
int
type
;
...
...
@@ -68,6 +80,12 @@ public abstract class Atom {
this
.
type
=
type
;
}
@Override
public
String
toString
()
{
return
getAtomTypeString
(
type
);
}
/** An MP4 atom that is a leaf. */
public
static
final
class
LeafAtom
extends
Atom
{
public
final
ParsableByteArray
data
;
...
...
@@ -79,43 +97,75 @@ public abstract class Atom {
}
/** An MP4 atom that has child atoms. */
public
static
final
class
ContainerAtom
extends
Atom
{
public
final
ArrayList
<
Atom
>
children
;
public
final
int
endByteOffset
;
public
final
long
endByteOffset
;
public
final
List
<
LeafAtom
>
leafChildren
;
public
final
List
<
ContainerAtom
>
containerChildren
;
public
ContainerAtom
(
int
type
,
int
endByteOffset
)
{
public
ContainerAtom
(
int
type
,
long
endByteOffset
)
{
super
(
type
);
leafChildren
=
new
ArrayList
<
LeafAtom
>();
containerChildren
=
new
ArrayList
<
ContainerAtom
>();
this
.
endByteOffset
=
endByteOffset
;
children
=
new
ArrayList
<
Atom
>();
}
public
void
add
(
Atom
atom
)
{
children
.
add
(
atom
);
public
void
add
(
LeafAtom
atom
)
{
leafChildren
.
add
(
atom
);
}
public
void
add
(
ContainerAtom
atom
)
{
containerChildren
.
add
(
atom
);
}
public
LeafAtom
getLeafAtomOfType
(
int
type
)
{
int
childrenSize
=
c
hildren
.
size
();
int
childrenSize
=
leafC
hildren
.
size
();
for
(
int
i
=
0
;
i
<
childrenSize
;
i
++)
{
Atom
atom
=
c
hildren
.
get
(
i
);
LeafAtom
atom
=
leafC
hildren
.
get
(
i
);
if
(
atom
.
type
==
type
)
{
return
(
LeafAtom
)
atom
;
return
atom
;
}
}
return
null
;
}
public
ContainerAtom
getContainerAtomOfType
(
int
type
)
{
int
childrenSize
=
children
.
size
();
int
childrenSize
=
c
ontainerC
hildren
.
size
();
for
(
int
i
=
0
;
i
<
childrenSize
;
i
++)
{
Atom
atom
=
c
hildren
.
get
(
i
);
ContainerAtom
atom
=
containerC
hildren
.
get
(
i
);
if
(
atom
.
type
==
type
)
{
return
(
ContainerAtom
)
atom
;
return
atom
;
}
}
return
null
;
}
@Override
public
String
toString
()
{
return
getAtomTypeString
(
type
)
+
" leaves: "
+
Arrays
.
toString
(
leafChildren
.
toArray
(
new
LeafAtom
[
0
]))
+
" containers: "
+
Arrays
.
toString
(
containerChildren
.
toArray
(
new
ContainerAtom
[
0
]));
}
}
private
static
String
getAtomTypeString
(
int
type
)
{
return
""
+
(
char
)
(
type
>>
24
)
+
(
char
)
((
type
>>
16
)
&
0xFF
)
+
(
char
)
((
type
>>
8
)
&
0xFF
)
+
(
char
)
(
type
&
0xFF
);
}
private
static
int
getAtomTypeInteger
(
String
typeName
)
{
Assertions
.
checkArgument
(
typeName
.
length
()
==
4
);
int
result
=
0
;
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
result
<<=
8
;
result
|=
typeName
.
charAt
(
i
);
}
return
result
;
}
}
library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
0 → 100644
View file @
876fa41b
/*
* 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
.
mp4
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
import
android.util.Pair
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
public
final
class
CommonMp4AtomParsers
{
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
private
static
final
int
[]
AC3_CHANNEL_COUNTS
=
new
int
[]
{
2
,
1
,
2
,
3
,
3
,
4
,
4
,
5
};
/** Nominal bit-rates for AC-3 audio in kbps, indexed by bit_rate_code. (See ETSI TS 102 366.) */
private
static
final
int
[]
AC3_BIT_RATES
=
new
int
[]
{
32
,
40
,
48
,
56
,
64
,
80
,
96
,
112
,
128
,
160
,
192
,
224
,
256
,
320
,
384
,
448
,
512
,
576
,
640
};
/**
* Parses a trak atom (defined in 14496-12).
*
* @return A {@link Track} instance.
*/
public
static
Track
parseTrak
(
Atom
.
ContainerAtom
trak
)
{
Atom
.
ContainerAtom
mdia
=
trak
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
);
int
trackType
=
parseHdlr
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_hdlr
).
data
);
Assertions
.
checkState
(
trackType
==
Track
.
TYPE_AUDIO
||
trackType
==
Track
.
TYPE_VIDEO
||
trackType
==
Track
.
TYPE_TEXT
||
trackType
==
Track
.
TYPE_TIME_CODE
);
Pair
<
Integer
,
Long
>
header
=
parseTkhd
(
trak
.
getLeafAtomOfType
(
Atom
.
TYPE_tkhd
).
data
);
int
id
=
header
.
first
;
long
duration
=
header
.
second
;
long
timescale
=
parseMdhd
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_mdhd
).
data
);
long
durationUs
;
if
(
duration
==
-
1
)
{
durationUs
=
C
.
UNKNOWN_TIME_US
;
}
else
{
durationUs
=
Util
.
scaleLargeTimestamp
(
duration
,
C
.
MICROS_PER_SECOND
,
timescale
);
}
Atom
.
ContainerAtom
stbl
=
mdia
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
)
.
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
Pair
<
MediaFormat
,
TrackEncryptionBox
[]>
sampleDescriptions
=
parseStsd
(
stbl
.
getLeafAtomOfType
(
Atom
.
TYPE_stsd
).
data
);
return
new
Track
(
id
,
trackType
,
timescale
,
durationUs
,
sampleDescriptions
.
first
,
sampleDescriptions
.
second
);
}
/**
* Parses a tkhd atom (defined in 14496-12).
*
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
* the movie header box). The duration is set to -1 if the duration is unspecified.
*/
private
static
Pair
<
Integer
,
Long
>
parseTkhd
(
ParsableByteArray
tkhd
)
{
tkhd
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
tkhd
.
readInt
();
int
version
=
Mp4Util
.
parseFullAtomVersion
(
fullAtom
);
tkhd
.
skip
(
version
==
0
?
8
:
16
);
int
trackId
=
tkhd
.
readInt
();
tkhd
.
skip
(
4
);
boolean
durationUnknown
=
true
;
int
durationPosition
=
tkhd
.
getPosition
();
int
durationByteCount
=
version
==
0
?
4
:
8
;
for
(
int
i
=
0
;
i
<
durationByteCount
;
i
++)
{
if
(
tkhd
.
data
[
durationPosition
+
i
]
!=
-
1
)
{
durationUnknown
=
false
;
break
;
}
}
long
duration
;
if
(
durationUnknown
)
{
tkhd
.
skip
(
durationByteCount
);
duration
=
-
1
;
}
else
{
duration
=
version
==
0
?
tkhd
.
readUnsignedInt
()
:
tkhd
.
readUnsignedLongToLong
();
}
return
Pair
.
create
(
trackId
,
duration
);
}
/**
* Parses an hdlr atom.
*
* @param hdlr The hdlr atom to parse.
* @return The track type.
*/
private
static
int
parseHdlr
(
ParsableByteArray
hdlr
)
{
hdlr
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
+
4
);
return
hdlr
.
readInt
();
}
/**
* Parses an mdhd atom (defined in 14496-12).
*
* @param mdhd The mdhd atom to parse.
* @return The media timescale, defined as the number of time units that pass in one second.
*/
private
static
long
parseMdhd
(
ParsableByteArray
mdhd
)
{
mdhd
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
mdhd
.
readInt
();
int
version
=
Mp4Util
.
parseFullAtomVersion
(
fullAtom
);
mdhd
.
skip
(
version
==
0
?
8
:
16
);
return
mdhd
.
readUnsignedInt
();
}
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
[]>
parseStsd
(
ParsableByteArray
stsd
)
{
stsd
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
);
int
numberOfEntries
=
stsd
.
readInt
();
MediaFormat
mediaFormat
=
null
;
TrackEncryptionBox
[]
trackEncryptionBoxes
=
new
TrackEncryptionBox
[
numberOfEntries
];
for
(
int
i
=
0
;
i
<
numberOfEntries
;
i
++)
{
int
childStartPosition
=
stsd
.
getPosition
();
int
childAtomSize
=
stsd
.
readInt
();
Assertions
.
checkArgument
(
childAtomSize
>
0
,
"childAtomSize should be positive"
);
int
childAtomType
=
stsd
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_avc1
||
childAtomType
==
Atom
.
TYPE_avc3
||
childAtomType
==
Atom
.
TYPE_encv
)
{
Pair
<
MediaFormat
,
TrackEncryptionBox
>
avc
=
parseAvcFromParent
(
stsd
,
childStartPosition
,
childAtomSize
);
mediaFormat
=
avc
.
first
;
trackEncryptionBoxes
[
i
]
=
avc
.
second
;
}
else
if
(
childAtomType
==
Atom
.
TYPE_mp4a
||
childAtomType
==
Atom
.
TYPE_enca
||
childAtomType
==
Atom
.
TYPE_ac_3
)
{
Pair
<
MediaFormat
,
TrackEncryptionBox
>
audioSampleEntry
=
parseAudioSampleEntry
(
stsd
,
childAtomType
,
childStartPosition
,
childAtomSize
);
mediaFormat
=
audioSampleEntry
.
first
;
trackEncryptionBoxes
[
i
]
=
audioSampleEntry
.
second
;
}
else
if
(
childAtomType
==
Atom
.
TYPE_TTML
)
{
mediaFormat
=
MediaFormat
.
createTtmlFormat
();
}
else
if
(
childAtomType
==
Atom
.
TYPE_mp4v
)
{
mediaFormat
=
parseMp4vFromParent
(
stsd
,
childStartPosition
,
childAtomSize
);
}
stsd
.
setPosition
(
childStartPosition
+
childAtomSize
);
}
return
Pair
.
create
(
mediaFormat
,
trackEncryptionBoxes
);
}
/** Returns the media format for an avc1 box. */
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAvcFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
);
parent
.
skip
(
24
);
int
width
=
parent
.
readUnsignedShort
();
int
height
=
parent
.
readUnsignedShort
();
float
pixelWidthHeightRatio
=
1
;
parent
.
skip
(
50
);
List
<
byte
[]>
initializationData
=
null
;
TrackEncryptionBox
trackEncryptionBox
=
null
;
int
childPosition
=
parent
.
getPosition
();
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childStartPosition
=
parent
.
getPosition
();
int
childAtomSize
=
parent
.
readInt
();
if
(
childAtomSize
==
0
&&
parent
.
getPosition
()
-
position
==
size
)
{
// Handle optional terminating four zero bytes in MOV files.
break
;
}
Assertions
.
checkArgument
(
childAtomSize
>
0
,
"childAtomSize should be positive"
);
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_avcC
)
{
initializationData
=
parseAvcCFromParent
(
parent
,
childStartPosition
);
}
else
if
(
childAtomType
==
Atom
.
TYPE_sinf
)
{
trackEncryptionBox
=
parseSinfFromParent
(
parent
,
childStartPosition
,
childAtomSize
);
}
else
if
(
childAtomType
==
Atom
.
TYPE_pasp
)
{
pixelWidthHeightRatio
=
parsePaspFromParent
(
parent
,
childStartPosition
);
}
childPosition
+=
childAtomSize
;
}
MediaFormat
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_H264
,
MediaFormat
.
NO_VALUE
,
width
,
height
,
pixelWidthHeightRatio
,
initializationData
);
return
Pair
.
create
(
format
,
trackEncryptionBox
);
}
private
static
List
<
byte
[]>
parseAvcCFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
+
4
);
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
int
nalUnitLength
=
(
parent
.
readUnsignedByte
()
&
0x3
)
+
1
;
if
(
nalUnitLength
!=
4
)
{
// readSample currently relies on a nalUnitLength of 4.
// TODO: Consider handling the case where it isn't.
throw
new
IllegalStateException
();
}
List
<
byte
[]>
initializationData
=
new
ArrayList
<
byte
[]>();
// TODO: We should try and parse these using CodecSpecificDataUtil.parseSpsNalUnit, and
// expose the AVC profile and level somewhere useful; Most likely in MediaFormat.
int
numSequenceParameterSets
=
parent
.
readUnsignedByte
()
&
0x1F
;
for
(
int
j
=
0
;
j
<
numSequenceParameterSets
;
j
++)
{
initializationData
.
add
(
Mp4Util
.
parseChildNalUnit
(
parent
));
}
int
numPictureParameterSets
=
parent
.
readUnsignedByte
();
for
(
int
j
=
0
;
j
<
numPictureParameterSets
;
j
++)
{
initializationData
.
add
(
Mp4Util
.
parseChildNalUnit
(
parent
));
}
return
initializationData
;
}
private
static
TrackEncryptionBox
parseSinfFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
int
childPosition
=
position
+
Mp4Util
.
ATOM_HEADER_SIZE
;
TrackEncryptionBox
trackEncryptionBox
=
null
;
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childAtomSize
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_frma
)
{
parent
.
readInt
();
// dataFormat.
}
else
if
(
childAtomType
==
Atom
.
TYPE_schm
)
{
parent
.
skip
(
4
);
parent
.
readInt
();
// schemeType. Expect cenc
parent
.
readInt
();
// schemeVersion. Expect 0x00010000
}
else
if
(
childAtomType
==
Atom
.
TYPE_schi
)
{
trackEncryptionBox
=
parseSchiFromParent
(
parent
,
childPosition
,
childAtomSize
);
}
childPosition
+=
childAtomSize
;
}
return
trackEncryptionBox
;
}
private
static
float
parsePaspFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
);
int
hSpacing
=
parent
.
readUnsignedIntToInt
();
int
vSpacing
=
parent
.
readUnsignedIntToInt
();
return
(
float
)
hSpacing
/
vSpacing
;
}
private
static
TrackEncryptionBox
parseSchiFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
int
childPosition
=
position
+
Mp4Util
.
ATOM_HEADER_SIZE
;
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childAtomSize
=
parent
.
readInt
();
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_tenc
)
{
parent
.
skip
(
4
);
int
firstInt
=
parent
.
readInt
();
boolean
defaultIsEncrypted
=
(
firstInt
>>
8
)
==
1
;
int
defaultInitVectorSize
=
firstInt
&
0xFF
;
byte
[]
defaultKeyId
=
new
byte
[
16
];
parent
.
readBytes
(
defaultKeyId
,
0
,
defaultKeyId
.
length
);
return
new
TrackEncryptionBox
(
defaultIsEncrypted
,
defaultInitVectorSize
,
defaultKeyId
);
}
childPosition
+=
childAtomSize
;
}
return
null
;
}
/** Returns the media format for an mp4v box. */
private
static
MediaFormat
parseMp4vFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
);
parent
.
skip
(
24
);
int
width
=
parent
.
readUnsignedShort
();
int
height
=
parent
.
readUnsignedShort
();
parent
.
skip
(
50
);
List
<
byte
[]>
initializationData
=
new
ArrayList
<
byte
[]>(
1
);
int
childPosition
=
parent
.
getPosition
();
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childStartPosition
=
parent
.
getPosition
();
int
childAtomSize
=
parent
.
readInt
();
Assertions
.
checkArgument
(
childAtomSize
>
0
,
"childAtomSize should be positive"
);
int
childAtomType
=
parent
.
readInt
();
if
(
childAtomType
==
Atom
.
TYPE_esds
)
{
initializationData
.
add
(
parseEsdsFromParent
(
parent
,
childStartPosition
));
}
childPosition
+=
childAtomSize
;
}
return
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_MP4V
,
MediaFormat
.
NO_VALUE
,
width
,
height
,
initializationData
);
}
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAudioSampleEntry
(
ParsableByteArray
parent
,
int
atomType
,
int
position
,
int
size
)
{
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
);
parent
.
skip
(
16
);
int
channelCount
=
parent
.
readUnsignedShort
();
int
sampleSize
=
parent
.
readUnsignedShort
();
parent
.
skip
(
4
);
int
sampleRate
=
parent
.
readUnsignedFixedPoint1616
();
int
bitrate
=
MediaFormat
.
NO_VALUE
;
byte
[]
initializationData
=
null
;
TrackEncryptionBox
trackEncryptionBox
=
null
;
int
childPosition
=
parent
.
getPosition
();
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childStartPosition
=
parent
.
getPosition
();
int
childAtomSize
=
parent
.
readInt
();
Assertions
.
checkArgument
(
childAtomSize
>
0
,
"childAtomSize should be positive"
);
int
childAtomType
=
parent
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_mp4a
||
atomType
==
Atom
.
TYPE_enca
)
{
if
(
childAtomType
==
Atom
.
TYPE_esds
)
{
initializationData
=
parseEsdsFromParent
(
parent
,
childStartPosition
);
// TODO: Do we really need to do this? See [Internal: b/10903778]
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data.
Pair
<
Integer
,
Integer
>
audioSpecificConfig
=
CodecSpecificDataUtil
.
parseAudioSpecificConfig
(
initializationData
);
sampleRate
=
audioSpecificConfig
.
first
;
channelCount
=
audioSpecificConfig
.
second
;
}
else
if
(
childAtomType
==
Atom
.
TYPE_sinf
)
{
trackEncryptionBox
=
parseSinfFromParent
(
parent
,
childStartPosition
,
childAtomSize
);
}
}
else
if
(
atomType
==
Atom
.
TYPE_ac_3
&&
childAtomType
==
Atom
.
TYPE_dac3
)
{
// TODO: Choose the right AC-3 track based on the contents of dac3/dec3.
Ac3Format
ac3Format
=
parseAc3SpecificBoxFromParent
(
parent
,
childStartPosition
);
if
(
ac3Format
!=
null
)
{
sampleRate
=
ac3Format
.
sampleRate
;
channelCount
=
ac3Format
.
channelCount
;
bitrate
=
ac3Format
.
bitrate
;
}
// TODO: Add support for encrypted AC-3.
trackEncryptionBox
=
null
;
}
else
if
(
atomType
==
Atom
.
TYPE_ec_3
&&
childAtomType
==
Atom
.
TYPE_dec3
)
{
sampleRate
=
parseEc3SpecificBoxFromParent
(
parent
,
childStartPosition
);
trackEncryptionBox
=
null
;
}
childPosition
+=
childAtomSize
;
}
String
mimeType
;
if
(
atomType
==
Atom
.
TYPE_ac_3
)
{
mimeType
=
MimeTypes
.
AUDIO_AC3
;
}
else
if
(
atomType
==
Atom
.
TYPE_ec_3
)
{
mimeType
=
MimeTypes
.
AUDIO_EC3
;
}
else
{
mimeType
=
MimeTypes
.
AUDIO_AAC
;
}
MediaFormat
format
=
MediaFormat
.
createAudioFormat
(
mimeType
,
sampleSize
,
channelCount
,
sampleRate
,
bitrate
,
initializationData
==
null
?
null
:
Collections
.
singletonList
(
initializationData
));
return
Pair
.
create
(
format
,
trackEncryptionBox
);
}
/** Returns codec-specific initialization data contained in an esds box. */
private
static
byte
[]
parseEsdsFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
+
4
);
// Start of the ES_Descriptor (defined in 14496-1)
parent
.
skip
(
1
);
// ES_Descriptor tag
int
varIntByte
=
parent
.
readUnsignedByte
();
while
(
varIntByte
>
127
)
{
varIntByte
=
parent
.
readUnsignedByte
();
}
parent
.
skip
(
2
);
// ES_ID
int
flags
=
parent
.
readUnsignedByte
();
if
((
flags
&
0x80
/* streamDependenceFlag */
)
!=
0
)
{
parent
.
skip
(
2
);
}
if
((
flags
&
0x40
/* URL_Flag */
)
!=
0
)
{
parent
.
skip
(
parent
.
readUnsignedShort
());
}
if
((
flags
&
0x20
/* OCRstreamFlag */
)
!=
0
)
{
parent
.
skip
(
2
);
}
// Start of the DecoderConfigDescriptor (defined in 14496-1)
parent
.
skip
(
1
);
// DecoderConfigDescriptor tag
varIntByte
=
parent
.
readUnsignedByte
();
while
(
varIntByte
>
127
)
{
varIntByte
=
parent
.
readUnsignedByte
();
}
parent
.
skip
(
13
);
// Start of AudioSpecificConfig (defined in 14496-3)
parent
.
skip
(
1
);
// AudioSpecificConfig tag
varIntByte
=
parent
.
readUnsignedByte
();
int
varInt
=
varIntByte
&
0x7F
;
while
(
varIntByte
>
127
)
{
varIntByte
=
parent
.
readUnsignedByte
();
varInt
=
varInt
<<
8
;
varInt
|=
varIntByte
&
0x7F
;
}
byte
[]
initializationData
=
new
byte
[
varInt
];
parent
.
readBytes
(
initializationData
,
0
,
varInt
);
return
initializationData
;
}
private
static
Ac3Format
parseAc3SpecificBoxFromParent
(
ParsableByteArray
parent
,
int
position
)
{
// Start of the dac3 atom (defined in ETSI TS 102 366)
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
);
// fscod (sample rate code)
int
fscod
=
(
parent
.
readUnsignedByte
()
&
0xC0
)
>>
6
;
int
sampleRate
;
switch
(
fscod
)
{
case
0
:
sampleRate
=
48000
;
break
;
case
1
:
sampleRate
=
44100
;
break
;
case
2
:
sampleRate
=
32000
;
break
;
default
:
// TODO: The decoder should not use this stream.
return
null
;
}
int
nextByte
=
parent
.
readUnsignedByte
();
// Map acmod (audio coding mode) onto a channel count.
int
channelCount
=
AC3_CHANNEL_COUNTS
[(
nextByte
&
0x38
)
>>
3
];
// lfeon (low frequency effects on)
if
((
nextByte
&
0x04
)
!=
0
)
{
channelCount
++;
}
// Map bit_rate_code onto a bit-rate in kbit/s.
int
bitrate
=
AC3_BIT_RATES
[((
nextByte
&
0x03
)
<<
3
)
+
(
parent
.
readUnsignedByte
()
>>
5
)];
return
new
Ac3Format
(
channelCount
,
sampleRate
,
bitrate
);
}
private
static
int
parseEc3SpecificBoxFromParent
(
ParsableByteArray
parent
,
int
position
)
{
// Start of the dec3 atom (defined in ETSI TS 102 366)
parent
.
setPosition
(
position
+
Mp4Util
.
ATOM_HEADER_SIZE
);
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
return
0
;
}
private
CommonMp4AtomParsers
()
{
// Prevent instantiation.
}
/** Represents the format for AC-3 audio. */
private
static
final
class
Ac3Format
{
public
final
int
channelCount
;
public
final
int
sampleRate
;
public
final
int
bitrate
;
public
Ac3Format
(
int
channelCount
,
int
sampleRate
,
int
bitrate
)
{
this
.
channelCount
=
channelCount
;
this
.
sampleRate
=
sampleRate
;
this
.
bitrate
=
bitrate
;
}
}
}
library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
0 → 100644
View file @
876fa41b
/*
* 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
.
mp4
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.nio.ByteBuffer
;
/**
* Utility methods and constants for parsing fragmented and unfragmented MP4 files.
*/
public
final
class
Mp4Util
{
/** Size of an atom header, in bytes. */
public
static
final
int
ATOM_HEADER_SIZE
=
8
;
/** Size of a long atom header, in bytes. */
public
static
final
int
LONG_ATOM_HEADER_SIZE
=
16
;
/** Size of a full atom header, in bytes. */
public
static
final
int
FULL_ATOM_HEADER_SIZE
=
12
;
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
/** Parses the version number out of the additional integer component of a full atom. */
public
static
int
parseFullAtomVersion
(
int
fullAtomInt
)
{
return
0x000000FF
&
(
fullAtomInt
>>
24
);
}
/** Parses the atom flags out of the additional integer component of a full atom. */
public
static
int
parseFullAtomFlags
(
int
fullAtomInt
)
{
return
0x00FFFFFF
&
fullAtomInt
;
}
/**
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
* assumed that the top bit will always be set to zero.
*
* @throws IllegalArgumentException If the top bit of the input data is set.
*/
public
static
int
readUnsignedIntToInt
(
ByteBuffer
data
)
{
int
result
=
0xFF
&
data
.
get
();
for
(
int
i
=
1
;
i
<
4
;
i
++)
{
result
<<=
8
;
result
|=
0xFF
&
data
.
get
();
}
if
(
result
<
0
)
{
throw
new
IllegalArgumentException
(
"Top bit not zero: "
+
result
);
}
return
result
;
}
/**
* Replaces length prefixes of NAL units in {@code buffer} with start code prefixes, within the
* {@code size} bytes preceding the buffer's position.
*/
public
static
void
replaceLengthPrefixesWithAvcStartCodes
(
ByteBuffer
buffer
,
int
size
)
{
int
sampleOffset
=
buffer
.
position
()
-
size
;
int
position
=
sampleOffset
;
while
(
position
<
sampleOffset
+
size
)
{
buffer
.
position
(
position
);
int
length
=
readUnsignedIntToInt
(
buffer
);
buffer
.
position
(
position
);
buffer
.
put
(
NAL_START_CODE
);
position
+=
length
+
4
;
}
buffer
.
position
(
sampleOffset
+
size
);
}
/** Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. */
public
static
byte
[]
parseChildNalUnit
(
ParsableByteArray
atom
)
{
int
length
=
atom
.
readUnsignedShort
();
int
offset
=
atom
.
getPosition
();
atom
.
skip
(
length
);
return
CodecSpecificDataUtil
.
buildNalUnit
(
atom
.
data
,
offset
,
length
);
}
}
library/src/main/java/com/google/android/exoplayer/mp4/Track.java
View file @
876fa41b
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox
;
...
...
@@ -43,6 +44,10 @@ public final class Track {
* Type of a meta track.
*/
public
static
final
int
TYPE_META
=
0x6D657461
;
/**
* Type of a time-code track.
*/
public
static
final
int
TYPE_TIME_CODE
=
0x746D6364
;
/**
* The track identifier.
...
...
@@ -50,7 +55,8 @@ public final class Track {
public
final
int
id
;
/**
* One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT} and {@link #TYPE_META}.
* One of {@link #TYPE_VIDEO}, {@link #TYPE_AUDIO}, {@link #TYPE_HINT}, {@link #TYPE_META} and
* {@link #TYPE_TIME_CODE}.
*/
public
final
int
type
;
...
...
@@ -60,6 +66,11 @@ public final class Track {
public
final
long
timescale
;
/**
* The duration of the track in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown.
*/
public
final
long
durationUs
;
/**
* The format if {@link #type} is {@link #TYPE_VIDEO} or {@link #TYPE_AUDIO}. Null otherwise.
*/
public
final
MediaFormat
mediaFormat
;
...
...
@@ -69,11 +80,12 @@ public final class Track {
*/
public
final
TrackEncryptionBox
[]
sampleDescriptionEncryptionBoxes
;
public
Track
(
int
id
,
int
type
,
long
timescale
,
MediaFormat
mediaFormat
,
public
Track
(
int
id
,
int
type
,
long
timescale
,
long
durationUs
,
MediaFormat
mediaFormat
,
TrackEncryptionBox
[]
sampleDescriptionEncryptionBoxes
)
{
this
.
id
=
id
;
this
.
type
=
type
;
this
.
timescale
=
timescale
;
this
.
durationUs
=
durationUs
;
this
.
mediaFormat
=
mediaFormat
;
this
.
sampleDescriptionEncryptionBoxes
=
sampleDescriptionEncryptionBoxes
;
}
...
...
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
View file @
876fa41b
...
...
@@ -167,8 +167,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
:
Track
.
TYPE_AUDIO
;
FragmentedMp4Extractor
extractor
=
new
FragmentedMp4Extractor
(
FragmentedMp4Extractor
.
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
);
extractor
.
setTrack
(
new
Track
(
trackIndex
,
trackType
,
streamElement
.
timescale
,
mediaFormat
,
trackEncryptionBoxes
));
extractor
.
setTrack
(
new
Track
(
trackIndex
,
trackType
,
streamElement
.
timescale
,
initialManifest
.
durationUs
,
mediaFormat
,
trackEncryptionBoxes
));
extractors
.
put
(
trackIndex
,
extractor
);
}
this
.
maxHeight
=
maxHeight
;
...
...
library/src/main/java/com/google/android/exoplayer/text/SubtitleView.java
View file @
876fa41b
...
...
@@ -123,7 +123,7 @@ public class SubtitleView extends View {
@Override
public
void
setBackgroundColor
(
int
color
)
{
backgroundColor
=
color
;
invalidate
(
);
forceUpdate
(
false
);
}
/**
...
...
@@ -134,8 +134,7 @@ public class SubtitleView extends View {
public
void
setText
(
CharSequence
text
)
{
textBuilder
.
setLength
(
0
);
textBuilder
.
append
(
text
);
hasMeasurements
=
false
;
requestLayout
();
forceUpdate
(
true
);
}
/**
...
...
@@ -147,9 +146,7 @@ public class SubtitleView extends View {
if
(
textPaint
.
getTextSize
()
!=
size
)
{
textPaint
.
setTextSize
(
size
);
innerPaddingX
=
(
int
)
(
size
*
INNER_PADDING_RATIO
+
0.5f
);
hasMeasurements
=
false
;
requestLayout
();
invalidate
();
forceUpdate
(
true
);
}
}
...
...
@@ -165,17 +162,22 @@ public class SubtitleView extends View {
edgeColor
=
style
.
edgeColor
;
setTypeface
(
style
.
typeface
);
super
.
setBackgroundColor
(
style
.
windowColor
);
hasMeasurements
=
false
;
requestLayout
();
forceUpdate
(
true
);
}
private
void
setTypeface
(
Typeface
typeface
)
{
if
(
textPaint
.
getTypeface
()
!=
typeface
)
{
textPaint
.
setTypeface
(
typeface
);
forceUpdate
(
true
);
}
}
private
void
forceUpdate
(
boolean
needsLayout
)
{
if
(
needsLayout
)
{
hasMeasurements
=
false
;
requestLayout
();
invalidate
();
}
invalidate
();
}
@Override
...
...
library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java
View file @
876fa41b
...
...
@@ -56,20 +56,28 @@ public class WebvttParser implements SubtitleParser {
private
static
final
Pattern
WEBVTT_METADATA_HEADER
=
Pattern
.
compile
(
WEBVTT_METADATA_HEADER_STRING
);
private
static
final
String
WEBVTT_CUE_IDENTIFIER_STRING
=
"^(?!.*(-->)).*$"
;
private
static
final
Pattern
WEBVTT_CUE_IDENTIFIER
=
Pattern
.
compile
(
WEBVTT_CUE_IDENTIFIER_STRING
);
private
static
final
String
WEBVTT_TIMESTAMP_STRING
=
"(\\d+:)?[0-5]\\d:[0-5]\\d\\.\\d{3}"
;
private
static
final
Pattern
WEBVTT_TIMESTAMP
=
Pattern
.
compile
(
WEBVTT_TIMESTAMP_STRING
);
private
static
final
Pattern
MEDIA_TIMESTAMP_OFFSET
=
Pattern
.
compile
(
OFFSET
+
"\\d+"
);
private
static
final
Pattern
MEDIA_TIMESTAMP
=
Pattern
.
compile
(
"MPEGTS:\\d+"
);
private
static
final
String
WEBVTT_CUE_TAG_STRING
=
"\\<.*?>"
;
private
final
boolean
strictParsing
;
private
final
boolean
filterTags
;
public
WebvttParser
()
{
this
(
true
);
this
(
true
,
true
);
}
public
WebvttParser
(
boolean
strictParsing
)
{
public
WebvttParser
(
boolean
strictParsing
,
boolean
filterTags
)
{
this
.
strictParsing
=
strictParsing
;
this
.
filterTags
=
filterTags
;
}
@Override
...
...
@@ -137,8 +145,15 @@ public class WebvttParser implements SubtitleParser {
// process the cues and text
while
((
line
=
webvttData
.
readLine
())
!=
null
)
{
// parse the cue identifier (if present) {
Matcher
matcher
=
WEBVTT_CUE_IDENTIFIER
.
matcher
(
line
);
if
(
matcher
.
find
())
{
// ignore the identifier (we currently don't use it) and read the next line
line
=
webvttData
.
readLine
();
}
// parse the cue timestamps
Matcher
matcher
=
WEBVTT_TIMESTAMP
.
matcher
(
line
);
matcher
=
WEBVTT_TIMESTAMP
.
matcher
(
line
);
long
startTime
;
long
endTime
;
String
text
=
""
;
...
...
@@ -159,7 +174,7 @@ public class WebvttParser implements SubtitleParser {
// parse text
while
(((
line
=
webvttData
.
readLine
())
!=
null
)
&&
(!
line
.
isEmpty
()))
{
text
+=
line
.
trim
(
)
+
"\n"
;
text
+=
processCueText
(
line
.
trim
()
)
+
"\n"
;
}
WebvttCue
cue
=
new
WebvttCue
(
startTime
,
endTime
,
text
);
...
...
@@ -193,6 +208,19 @@ public class WebvttParser implements SubtitleParser {
return
startTimeUs
;
}
protected
String
processCueText
(
String
line
)
{
if
(
filterTags
)
{
line
=
line
.
replaceAll
(
WEBVTT_CUE_TAG_STRING
,
""
);
line
=
line
.
replaceAll
(
"<"
,
"<"
);
line
=
line
.
replaceAll
(
">"
,
">"
);
line
=
line
.
replaceAll
(
" "
,
" "
);
line
=
line
.
replaceAll
(
"&"
,
"&"
);
return
line
;
}
else
{
return
line
;
}
}
protected
void
handleNoncompliantLine
(
String
line
)
throws
ParserException
{
if
(
strictParsing
)
{
throw
new
ParserException
(
"Unexpected line: "
+
line
);
...
...
library/src/main/java/com/google/android/exoplayer/upstream/ByteArrayDataSource.java
View file @
876fa41b
...
...
@@ -27,24 +27,27 @@ public class ByteArrayDataSource implements DataSource {
private
final
byte
[]
data
;
private
int
readPosition
;
private
int
remainingBytes
;
/**
* @param data The data to be read.
*/
public
ByteArrayDataSource
(
byte
[]
data
)
{
this
.
data
=
Assertions
.
checkNotNull
(
data
);
Assertions
.
checkNotNull
(
data
);
Assertions
.
checkArgument
(
data
.
length
>
0
);
this
.
data
=
data
;
}
@Override
public
long
open
(
DataSpec
dataSpec
)
throws
IOException
{
if
(
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
)
{
Assertions
.
checkArgument
(
dataSpec
.
position
<
data
.
length
);
}
else
{
Assertions
.
checkArgument
(
dataSpec
.
position
+
dataSpec
.
length
<=
data
.
length
);
}
readPosition
=
(
int
)
dataSpec
.
position
;
return
(
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
)
?
(
data
.
length
-
dataSpec
.
position
)
:
dataSpec
.
length
;
remainingBytes
=
(
int
)
((
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
)
?
(
data
.
length
-
dataSpec
.
position
)
:
dataSpec
.
length
);
if
(
remainingBytes
<=
0
||
readPosition
+
remainingBytes
>
data
.
length
)
{
throw
new
IOException
(
"Unsatisfiable range: ["
+
readPosition
+
", "
+
dataSpec
.
length
+
"], length: "
+
data
.
length
);
}
return
remainingBytes
;
}
@Override
...
...
@@ -54,8 +57,13 @@ public class ByteArrayDataSource implements DataSource {
@Override
public
int
read
(
byte
[]
buffer
,
int
offset
,
int
length
)
throws
IOException
{
if
(
remainingBytes
==
0
)
{
return
-
1
;
}
length
=
Math
.
min
(
length
,
remainingBytes
);
System
.
arraycopy
(
data
,
readPosition
,
buffer
,
offset
,
length
);
readPosition
+=
length
;
remainingBytes
-=
length
;
return
length
;
}
}
...
...
library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
View file @
876fa41b
...
...
@@ -29,6 +29,7 @@ public class MimeTypes {
public
static
final
String
VIDEO_WEBM
=
BASE_TYPE_VIDEO
+
"/webm"
;
public
static
final
String
VIDEO_H264
=
BASE_TYPE_VIDEO
+
"/avc"
;
public
static
final
String
VIDEO_VP9
=
BASE_TYPE_VIDEO
+
"/x-vnd.on2.vp9"
;
public
static
final
String
VIDEO_MP4V
=
BASE_TYPE_VIDEO
+
"/mp4v-es"
;
public
static
final
String
AUDIO_MP4
=
BASE_TYPE_AUDIO
+
"/mp4"
;
public
static
final
String
AUDIO_AAC
=
BASE_TYPE_AUDIO
+
"/mp4a-latm"
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment