mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
commit
8ec4aad001
50 changed files with 638 additions and 773 deletions
53
ISSUE_TEMPLATE → .github/ISSUE_TEMPLATE/bug.md
vendored
53
ISSUE_TEMPLATE → .github/ISSUE_TEMPLATE/bug.md
vendored
|
|
@ -1,13 +1,20 @@
|
||||||
Before filing an issue:
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Issue template for a bug report.
|
||||||
|
title: ''
|
||||||
|
labels: bug, 'needs triage'
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
Before filing a bug:
|
||||||
-----------------------
|
-----------------------
|
||||||
- Search existing issues, including issues that are closed.
|
- Search existing issues, including issues that are closed.
|
||||||
- Consult our FAQs, supported devices and supported formats pages. These can be
|
- Consult our FAQs, supported devices and supported formats pages. These can be
|
||||||
found at https://google.github.io/ExoPlayer/.
|
found at https://google.github.io/ExoPlayer/.
|
||||||
- Rule out issues in your own code. A good way to do this is to try and
|
- Rule out issues in your own code. A good way to do this is to try and
|
||||||
reproduce the issue in the ExoPlayer demo app.
|
reproduce the issue in the ExoPlayer demo app. Information about the ExoPlayer
|
||||||
- This issue tracker is intended for bugs, feature requests and ExoPlayer
|
demo app can be found here:
|
||||||
specific questions. If you're asking a general Android development question,
|
http://google.github.io/ExoPlayer/demo-application.html.
|
||||||
please do so on Stack Overflow.
|
|
||||||
|
|
||||||
When reporting a bug:
|
When reporting a bug:
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
@ -15,29 +22,33 @@ Fill out the sections below, leaving the headers but replacing the content. If
|
||||||
you're unable to provide certain information, please explain why in the relevant
|
you're unable to provide certain information, please explain why in the relevant
|
||||||
section. We may close issues if they do not include sufficient information.
|
section. We may close issues if they do not include sufficient information.
|
||||||
|
|
||||||
### Issue description
|
### [REQUIRED] Issue description
|
||||||
Describe the issue in detail, including observed and expected behavior.
|
Describe the issue in detail, including observed and expected behavior.
|
||||||
|
|
||||||
### Reproduction steps
|
### [REQUIRED] Reproduction steps
|
||||||
Describe how the issue can be reproduced, ideally using the ExoPlayer demo app.
|
Describe how the issue can be reproduced, ideally using the ExoPlayer demo app
|
||||||
|
or a small sample app that you’re able to share as source code on GitHub.
|
||||||
|
|
||||||
### Link to test content
|
### [REQUIRED] Link to test content
|
||||||
Provide a link to media that reproduces the issue. If you don't wish to post it
|
Provide a JSON snippet for the demo app’s media.exolist.json file, or a link to
|
||||||
publicly, please submit the issue, then email the link to
|
media that reproduces the issue. If you don't wish to post it publicly, please
|
||||||
dev.exoplayer@gmail.com using a subject in the format "Issue #1234".
|
submit the issue, then email the link to dev.exoplayer@gmail.com using a subject
|
||||||
|
in the format "Issue #1234". Provide all the metadata we'd need to play the
|
||||||
|
content like drm license urls or similar. If the content is accessible only in
|
||||||
|
certain countries or regions, please say so.
|
||||||
|
|
||||||
### Version of ExoPlayer being used
|
### [REQUIRED] A full bug report captured from the device
|
||||||
Specify the absolute version number. Avoid using terms such as "latest".
|
|
||||||
|
|
||||||
### Device(s) and version(s) of Android being used
|
|
||||||
Specify the devices and versions of Android on which the issue can be
|
|
||||||
reproduced, and how easily it reproduces. If possible, please test on multiple
|
|
||||||
devices and Android versions.
|
|
||||||
|
|
||||||
### A full bug report captured from the device
|
|
||||||
Capture a full bug report using "adb bugreport". Output from "adb logcat" or a
|
Capture a full bug report using "adb bugreport". Output from "adb logcat" or a
|
||||||
log snippet is NOT sufficient. Please attach the captured bug report as a file.
|
log snippet is NOT sufficient. Please attach the captured bug report as a file.
|
||||||
If you don't wish to post it publicly, please submit the issue, then email the
|
If you don't wish to post it publicly, please submit the issue, then email the
|
||||||
bug report to dev.exoplayer@gmail.com using a subject in the format
|
bug report to dev.exoplayer@gmail.com using a subject in the format
|
||||||
"Issue #1234".
|
"Issue #1234".
|
||||||
|
|
||||||
|
### [REQUIRED] Version of ExoPlayer being used
|
||||||
|
Specify the absolute version number. Avoid using terms such as "latest".
|
||||||
|
|
||||||
|
### [REQUIRED] Device(s) and version(s) of Android being used
|
||||||
|
Specify the devices and versions of Android on which the issue can be
|
||||||
|
reproduced, and how easily it reproduces. If possible, please test on multiple
|
||||||
|
devices and Android versions.
|
||||||
|
|
||||||
44
.github/ISSUE_TEMPLATE/content_not_playing.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/content_not_playing.md
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
---
|
||||||
|
name: Content not playing correctly
|
||||||
|
about: Issue template for a content not playing issue.
|
||||||
|
title: ''
|
||||||
|
labels: 'content not playing', 'needs triage'
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
Before filing a content issue:
|
||||||
|
------------------------------
|
||||||
|
- Search existing issues, including issues that are closed.
|
||||||
|
- Consult our supported formats page, which can be found at
|
||||||
|
https://google.github.io/ExoPlayer/supported-formats.html.
|
||||||
|
- Try playing your content in the ExoPlayer demo app. Information about the
|
||||||
|
ExoPlayer demo app can be found here:
|
||||||
|
http://google.github.io/ExoPlayer/demo-application.html.
|
||||||
|
|
||||||
|
When reporting a content issue:
|
||||||
|
-----------------------------
|
||||||
|
Fill out the sections below, leaving the headers but replacing the content. If
|
||||||
|
you're unable to provide certain information, please explain why in the relevant
|
||||||
|
section. We may close issues if they do not include sufficient information.
|
||||||
|
|
||||||
|
### [REQUIRED] Content description
|
||||||
|
Describe the content and any specifics you expected to play but did not. This
|
||||||
|
could be the container or sample format itself or any features the stream has
|
||||||
|
and you expect to play, like 5.1 audio track, text tracks or drm systems.
|
||||||
|
|
||||||
|
### [REQUIRED] Link to test content
|
||||||
|
Provide a JSON snippet for the demo app’s media.exolist.json file, or a link to
|
||||||
|
media that reproduces the issue. If you don't wish to post it publicly, please
|
||||||
|
submit the issue, then email the link to dev.exoplayer@gmail.com using a subject
|
||||||
|
in the format "Issue #1234". Provide all the metadata we'd need to play the
|
||||||
|
content like drm license urls or similar. If the content is accessible only in
|
||||||
|
certain countries or regions, please say so.
|
||||||
|
|
||||||
|
### [REQUIRED] Version of ExoPlayer being used
|
||||||
|
Specify the absolute version number. Avoid using terms such as "latest".
|
||||||
|
|
||||||
|
### [REQUIRED] Device(s) and version(s) of Android being used
|
||||||
|
Specify the devices and versions of Android on which you expect the content to
|
||||||
|
play. If possible, please test on multiple devices and Android versions.
|
||||||
|
|
||||||
|
|
||||||
31
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Issue template for a feature request.
|
||||||
|
title: ''
|
||||||
|
labels: enhancement, 'needs triage'
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
Before filing a feature request:
|
||||||
|
-----------------------
|
||||||
|
- Search existing open issues, specifically with the label ‘enhancement’.
|
||||||
|
- Search existing pull requests.
|
||||||
|
|
||||||
|
When filing a feature request:
|
||||||
|
-----------------------
|
||||||
|
Fill out the sections below, leaving the headers but replacing the content. If
|
||||||
|
you're unable to provide certain information, please explain why in the relevant
|
||||||
|
section. We may close issues if they do not include sufficient information.
|
||||||
|
|
||||||
|
### [REQUIRED] Use case description
|
||||||
|
Describe the use case or problem you are trying to solve in detail. If there are
|
||||||
|
any standards or specifications involved, please provide the relevant details.
|
||||||
|
|
||||||
|
### Proposed solution
|
||||||
|
A clear and concise description of your proposed solution, if you have one.
|
||||||
|
|
||||||
|
### Alternatives considered
|
||||||
|
A clear and concise description of any alternative solutions you considered,
|
||||||
|
if applicable.
|
||||||
|
|
||||||
|
|
||||||
31
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
name: Question
|
||||||
|
about: Issue template for a question.
|
||||||
|
title: ''
|
||||||
|
labels: question, 'needs triage'
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
Before filing a question:
|
||||||
|
-----------------------
|
||||||
|
- This issue tracker is intended ExoPlayer specific questions. If you're asking
|
||||||
|
a general Android development question, please do so on Stack Overflow.
|
||||||
|
- Search existing issues, including issues that are closed. It’s often the
|
||||||
|
quickest way to get an answer!
|
||||||
|
- Consult our FAQs, developer guide and the class reference of ExoPlayer. These
|
||||||
|
can be found at https://google.github.io/ExoPlayer/.
|
||||||
|
|
||||||
|
When filing a question:
|
||||||
|
-----------------------
|
||||||
|
Fill out the sections below, leaving the headers but replacing the content. If
|
||||||
|
you're unable to provide certain information, please explain why in the relevant
|
||||||
|
section. We may close issues if they do not include sufficient information.
|
||||||
|
|
||||||
|
### [REQUIRED] Searched documentation and issues
|
||||||
|
Tell us where you’ve already looked for an answer to your question. It’s
|
||||||
|
important for us to know this so that we can improve our documentation.
|
||||||
|
|
||||||
|
### [REQUIRED] Question
|
||||||
|
Describe your question in detail.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,5 +1,28 @@
|
||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### 2.9.6 ###
|
||||||
|
|
||||||
|
* Remove `player` and `isTopLevelSource` parameters from `MediaSource.prepare`.
|
||||||
|
* IMA extension:
|
||||||
|
* Require setting the `Player` on `AdsLoader` instances before
|
||||||
|
playback.
|
||||||
|
* Remove deprecated `ImaAdsMediaSource`. Create `AdsMediaSource` with an
|
||||||
|
`ImaAdsLoader` instead.
|
||||||
|
* Remove deprecated `AdsMediaSource` constructors. Listen for media source
|
||||||
|
events using `AdsMediaSource.addEventListener`, and ad interaction events by
|
||||||
|
adding a listener when building `ImaAdsLoader`.
|
||||||
|
* Allow apps to register playback-related obstructing views that are on top of
|
||||||
|
their ad display containers via `AdsLoader.AdViewProvider`. `PlayerView`
|
||||||
|
implements this interface and will register its control view. This makes it
|
||||||
|
possible for ad loading SDKs to calculate ad viewability accurately.
|
||||||
|
* DASH: Fix issue handling large `EventStream` presentation timestamps
|
||||||
|
([#5490](https://github.com/google/ExoPlayer/issues/5490)).
|
||||||
|
* HLS: Fix transition to STATE_ENDED when playing fragmented mp4 in chunkless
|
||||||
|
preparation ([#5524](https://github.com/google/ExoPlayer/issues/5524)).
|
||||||
|
* Revert workaround for video quality problems with Amlogic decoders, as this
|
||||||
|
may cause problems for some devices and/or non-interlaced content
|
||||||
|
([#5003](https://github.com/google/ExoPlayer/issues/5003)).
|
||||||
|
|
||||||
### 2.9.5 ###
|
### 2.9.5 ###
|
||||||
|
|
||||||
* HLS: Parse `CHANNELS` attribute from `EXT-X-MEDIA` tag.
|
* HLS: Parse `CHANNELS` attribute from `EXT-X-MEDIA` tag.
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
// ExoPlayer version and version code.
|
// ExoPlayer version and version code.
|
||||||
releaseVersion = '2.9.5'
|
releaseVersion = '2.9.6'
|
||||||
releaseVersionCode = 2009005
|
releaseVersionCode = 2009006
|
||||||
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
|
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
|
||||||
// components provided by the library may be of use on older devices.
|
// components provided by the library may be of use on older devices.
|
||||||
// However, please note that the core media playback functionality provided
|
// However, please note that the core media playback functionality provided
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package="com.google.android.exoplayer2.imademo">
|
package="com.google.android.exoplayer2.imademo">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-sdk/>
|
<uses-sdk/>
|
||||||
|
|
||||||
<application android:label="@string/application_name" android:icon="@mipmap/ic_launcher"
|
<application android:label="@string/application_name" android:icon="@mipmap/ic_launcher"
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,6 @@ import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||||
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
|
||||||
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
|
||||||
import com.google.android.exoplayer2.ui.PlayerView;
|
import com.google.android.exoplayer2.ui.PlayerView;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
|
@ -56,14 +52,9 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(Context context, PlayerView playerView) {
|
public void init(Context context, PlayerView playerView) {
|
||||||
// Create a default track selector.
|
|
||||||
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
|
|
||||||
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
|
||||||
|
|
||||||
// Create a player instance.
|
// Create a player instance.
|
||||||
player = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
|
player = ExoPlayerFactory.newSimpleInstance(context);
|
||||||
|
adsLoader.setPlayer(player);
|
||||||
// Bind the player to the view.
|
|
||||||
playerView.setPlayer(player);
|
playerView.setPlayer(player);
|
||||||
|
|
||||||
// This is the MediaSource representing the content media (i.e. not the ad).
|
// This is the MediaSource representing the content media (i.e. not the ad).
|
||||||
|
|
@ -73,10 +64,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
// Compose the content media source into a new AdsMediaSource with both ads and content.
|
// Compose the content media source into a new AdsMediaSource with both ads and content.
|
||||||
MediaSource mediaSourceWithAds =
|
MediaSource mediaSourceWithAds =
|
||||||
new AdsMediaSource(
|
new AdsMediaSource(
|
||||||
contentMediaSource,
|
contentMediaSource, /* adMediaSourceFactory= */ this, adsLoader, playerView);
|
||||||
/* adMediaSourceFactory= */ this,
|
|
||||||
adsLoader,
|
|
||||||
playerView.getOverlayFrameLayout());
|
|
||||||
|
|
||||||
// Prepare the player with the source.
|
// Prepare the player with the source.
|
||||||
player.seekTo(contentPosition);
|
player.seekTo(contentPosition);
|
||||||
|
|
@ -89,6 +77,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
contentPosition = player.getContentPosition();
|
contentPosition = player.getContentPosition();
|
||||||
player.release();
|
player.release();
|
||||||
player = null;
|
player = null;
|
||||||
|
adsLoader.setPlayer(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,7 @@ import android.util.Pair;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
@ -151,7 +149,6 @@ public class PlayerActivity extends Activity
|
||||||
|
|
||||||
private AdsLoader adsLoader;
|
private AdsLoader adsLoader;
|
||||||
private Uri loadedAdTagUri;
|
private Uri loadedAdTagUri;
|
||||||
private ViewGroup adUiViewGroup;
|
|
||||||
|
|
||||||
// Activity lifecycle
|
// Activity lifecycle
|
||||||
|
|
||||||
|
|
@ -474,7 +471,6 @@ public class PlayerActivity extends Activity
|
||||||
return buildMediaSource(uri, null);
|
return buildMediaSource(uri, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
|
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
|
||||||
@ContentType int type = Util.inferContentType(uri, overrideExtension);
|
@ContentType int type = Util.inferContentType(uri, overrideExtension);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -534,6 +530,9 @@ public class PlayerActivity extends Activity
|
||||||
mediaSource = null;
|
mediaSource = null;
|
||||||
trackSelector = null;
|
trackSelector = null;
|
||||||
}
|
}
|
||||||
|
if (adsLoader != null) {
|
||||||
|
adsLoader.setPlayer(null);
|
||||||
|
}
|
||||||
releaseMediaDrm();
|
releaseMediaDrm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -593,10 +592,8 @@ public class PlayerActivity extends Activity
|
||||||
.getConstructor(android.content.Context.class, android.net.Uri.class);
|
.getConstructor(android.content.Context.class, android.net.Uri.class);
|
||||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||||
adsLoader = loaderConstructor.newInstance(this, adTagUri);
|
adsLoader = loaderConstructor.newInstance(this, adTagUri);
|
||||||
adUiViewGroup = new FrameLayout(this);
|
|
||||||
// The demo app has a non-null overlay frame layout.
|
|
||||||
playerView.getOverlayFrameLayout().addView(adUiViewGroup);
|
|
||||||
}
|
}
|
||||||
|
adsLoader.setPlayer(player);
|
||||||
AdsMediaSource.MediaSourceFactory adMediaSourceFactory =
|
AdsMediaSource.MediaSourceFactory adMediaSourceFactory =
|
||||||
new AdsMediaSource.MediaSourceFactory() {
|
new AdsMediaSource.MediaSourceFactory() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -609,7 +606,7 @@ public class PlayerActivity extends Activity
|
||||||
return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
|
return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, adUiViewGroup);
|
return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView);
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
// IMA extension not loaded.
|
// IMA extension not loaded.
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||||
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
|
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
|
||||||
|
|
@ -46,7 +47,6 @@ import com.google.ads.interactivemedia.v3.api.player.VideoAdPlayer;
|
||||||
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
|
import com.google.ads.interactivemedia.v3.api.player.VideoProgressUpdate;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
|
@ -73,7 +73,17 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** Loads ads using the IMA SDK. All methods are called on the main thread. */
|
/**
|
||||||
|
* {@link AdsLoader} using the IMA SDK. All methods must be called on the main thread.
|
||||||
|
*
|
||||||
|
* <p>The player instance that will play the loaded ads must be set before playback using {@link
|
||||||
|
* #setPlayer(Player)}. If the ads loader is no longer required, it must be released by calling
|
||||||
|
* {@link #release()}.
|
||||||
|
*
|
||||||
|
* <p>The IMA SDK can take into account video control overlay views when calculating ad viewability.
|
||||||
|
* For more details see {@link AdDisplayContainer#registerVideoControlsOverlay(View)} and {@link
|
||||||
|
* AdViewProvider#getAdOverlayViews()}.
|
||||||
|
*/
|
||||||
public final class ImaAdsLoader
|
public final class ImaAdsLoader
|
||||||
implements Player.EventListener,
|
implements Player.EventListener,
|
||||||
AdsLoader,
|
AdsLoader,
|
||||||
|
|
@ -92,9 +102,9 @@ public final class ImaAdsLoader
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
private @Nullable ImaSdkSettings imaSdkSettings;
|
@Nullable private ImaSdkSettings imaSdkSettings;
|
||||||
private @Nullable AdEventListener adEventListener;
|
@Nullable private AdEventListener adEventListener;
|
||||||
private @Nullable Set<UiElement> adUiElements;
|
@Nullable private Set<UiElement> adUiElements;
|
||||||
private int vastLoadTimeoutMs;
|
private int vastLoadTimeoutMs;
|
||||||
private int mediaLoadTimeoutMs;
|
private int mediaLoadTimeoutMs;
|
||||||
private int mediaBitrate;
|
private int mediaBitrate;
|
||||||
|
|
@ -316,10 +326,11 @@ public final class ImaAdsLoader
|
||||||
private final AdDisplayContainer adDisplayContainer;
|
private final AdDisplayContainer adDisplayContainer;
|
||||||
private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
|
private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
|
||||||
|
|
||||||
|
@Nullable private Player nextPlayer;
|
||||||
private Object pendingAdRequestContext;
|
private Object pendingAdRequestContext;
|
||||||
private List<String> supportedMimeTypes;
|
private List<String> supportedMimeTypes;
|
||||||
private EventListener eventListener;
|
@Nullable private EventListener eventListener;
|
||||||
private Player player;
|
@Nullable private Player player;
|
||||||
private VideoProgressUpdate lastContentProgress;
|
private VideoProgressUpdate lastContentProgress;
|
||||||
private VideoProgressUpdate lastAdProgress;
|
private VideoProgressUpdate lastAdProgress;
|
||||||
private int lastVolumePercentage;
|
private int lastVolumePercentage;
|
||||||
|
|
@ -459,11 +470,11 @@ public final class ImaAdsLoader
|
||||||
}
|
}
|
||||||
imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE);
|
imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE);
|
||||||
imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION);
|
imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION);
|
||||||
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings);
|
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
adCallbacks = new ArrayList<>(/* initialCapacity= */ 1);
|
adCallbacks = new ArrayList<>(/* initialCapacity= */ 1);
|
||||||
adDisplayContainer = imaFactory.createAdDisplayContainer();
|
adDisplayContainer = imaFactory.createAdDisplayContainer();
|
||||||
adDisplayContainer.setPlayer(/* videoAdPlayer= */ this);
|
adDisplayContainer.setPlayer(/* videoAdPlayer= */ this);
|
||||||
|
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
|
||||||
adsLoader.addAdErrorListener(/* adErrorListener= */ this);
|
adsLoader.addAdErrorListener(/* adErrorListener= */ this);
|
||||||
adsLoader.addAdsLoadedListener(/* adsLoadedListener= */ this);
|
adsLoader.addAdsLoadedListener(/* adsLoadedListener= */ this);
|
||||||
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
||||||
|
|
@ -481,13 +492,29 @@ public final class ImaAdsLoader
|
||||||
return adsLoader;
|
return adsLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link AdDisplayContainer} used by this loader.
|
||||||
|
*
|
||||||
|
* <p>Note: any video controls overlays registered via {@link
|
||||||
|
* AdDisplayContainer#registerVideoControlsOverlay(View)} will be unregistered automatically when
|
||||||
|
* the media source detaches from this instance. It is therefore necessary to re-register views
|
||||||
|
* each time the ads loader is reused. Alternatively, provide overlay views via the {@link
|
||||||
|
* AdsLoader.AdViewProvider} when creating the media source to benefit from automatic
|
||||||
|
* registration.
|
||||||
|
*/
|
||||||
|
public AdDisplayContainer getAdDisplayContainer() {
|
||||||
|
return adDisplayContainer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the slots for displaying companion ads. Individual slots can be created using {@link
|
* Sets the slots for displaying companion ads. Individual slots can be created using {@link
|
||||||
* ImaSdkFactory#createCompanionAdSlot()}.
|
* ImaSdkFactory#createCompanionAdSlot()}.
|
||||||
*
|
*
|
||||||
* @param companionSlots Slots for displaying companion ads.
|
* @param companionSlots Slots for displaying companion ads.
|
||||||
* @see AdDisplayContainer#setCompanionSlots(Collection)
|
* @see AdDisplayContainer#setCompanionSlots(Collection)
|
||||||
|
* @deprecated Use {@code getAdDisplayContainer().setCompanionSlots(...)}.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public void setCompanionSlots(Collection<CompanionAdSlot> companionSlots) {
|
public void setCompanionSlots(Collection<CompanionAdSlot> companionSlots) {
|
||||||
adDisplayContainer.setCompanionSlots(companionSlots);
|
adDisplayContainer.setCompanionSlots(companionSlots);
|
||||||
}
|
}
|
||||||
|
|
@ -499,14 +526,14 @@ public final class ImaAdsLoader
|
||||||
* called, so it is only necessary to call this method if you want to request ads before preparing
|
* called, so it is only necessary to call this method if you want to request ads before preparing
|
||||||
* the player.
|
* the player.
|
||||||
*
|
*
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
* @param adViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
||||||
*/
|
*/
|
||||||
public void requestAds(ViewGroup adUiViewGroup) {
|
public void requestAds(ViewGroup adViewGroup) {
|
||||||
if (adPlaybackState != null || adsManager != null || pendingAdRequestContext != null) {
|
if (adPlaybackState != null || adsManager != null || pendingAdRequestContext != null) {
|
||||||
// Ads have already been requested.
|
// Ads have already been requested.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
adDisplayContainer.setAdContainer(adUiViewGroup);
|
adDisplayContainer.setAdContainer(adViewGroup);
|
||||||
pendingAdRequestContext = new Object();
|
pendingAdRequestContext = new Object();
|
||||||
AdsRequest request = imaFactory.createAdsRequest();
|
AdsRequest request = imaFactory.createAdsRequest();
|
||||||
if (adTagUri != null) {
|
if (adTagUri != null) {
|
||||||
|
|
@ -517,7 +544,6 @@ public final class ImaAdsLoader
|
||||||
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
|
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
|
||||||
request.setVastLoadTimeout(vastLoadTimeoutMs);
|
request.setVastLoadTimeout(vastLoadTimeoutMs);
|
||||||
}
|
}
|
||||||
request.setAdDisplayContainer(adDisplayContainer);
|
|
||||||
request.setContentProgressProvider(this);
|
request.setContentProgressProvider(this);
|
||||||
request.setUserRequestContext(pendingAdRequestContext);
|
request.setUserRequestContext(pendingAdRequestContext);
|
||||||
adsLoader.requestAds(request);
|
adsLoader.requestAds(request);
|
||||||
|
|
@ -525,6 +551,14 @@ public final class ImaAdsLoader
|
||||||
|
|
||||||
// AdsLoader implementation.
|
// AdsLoader implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPlayer(@Nullable Player player) {
|
||||||
|
Assertions.checkState(Looper.getMainLooper() == Looper.myLooper());
|
||||||
|
Assertions.checkState(
|
||||||
|
player == null || player.getApplicationLooper() == Looper.getMainLooper());
|
||||||
|
nextPlayer = player;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSupportedContentTypes(@C.ContentType int... contentTypes) {
|
public void setSupportedContentTypes(@C.ContentType int... contentTypes) {
|
||||||
List<String> supportedMimeTypes = new ArrayList<>();
|
List<String> supportedMimeTypes = new ArrayList<>();
|
||||||
|
|
@ -549,14 +583,20 @@ public final class ImaAdsLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup) {
|
public void start(EventListener eventListener, AdViewProvider adViewProvider) {
|
||||||
Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper());
|
Assertions.checkNotNull(
|
||||||
this.player = player;
|
nextPlayer, "Set player using adsLoader.setPlayer before preparing the player.");
|
||||||
|
player = nextPlayer;
|
||||||
this.eventListener = eventListener;
|
this.eventListener = eventListener;
|
||||||
lastVolumePercentage = 0;
|
lastVolumePercentage = 0;
|
||||||
lastAdProgress = null;
|
lastAdProgress = null;
|
||||||
lastContentProgress = null;
|
lastContentProgress = null;
|
||||||
adDisplayContainer.setAdContainer(adUiViewGroup);
|
ViewGroup adViewGroup = adViewProvider.getAdViewGroup();
|
||||||
|
adDisplayContainer.setAdContainer(adViewGroup);
|
||||||
|
View[] adOverlayViews = adViewProvider.getAdOverlayViews();
|
||||||
|
for (View view : adOverlayViews) {
|
||||||
|
adDisplayContainer.registerVideoControlsOverlay(view);
|
||||||
|
}
|
||||||
player.addListener(this);
|
player.addListener(this);
|
||||||
maybeNotifyPendingAdLoadError();
|
maybeNotifyPendingAdLoadError();
|
||||||
if (adPlaybackState != null) {
|
if (adPlaybackState != null) {
|
||||||
|
|
@ -570,12 +610,12 @@ public final class ImaAdsLoader
|
||||||
startAdPlayback();
|
startAdPlayback();
|
||||||
} else {
|
} else {
|
||||||
// Ads haven't loaded yet, so request them.
|
// Ads haven't loaded yet, so request them.
|
||||||
requestAds(adUiViewGroup);
|
requestAds(adViewGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void detachPlayer() {
|
public void stop() {
|
||||||
if (adsManager != null && imaPausedContent) {
|
if (adsManager != null && imaPausedContent) {
|
||||||
adPlaybackState =
|
adPlaybackState =
|
||||||
adPlaybackState.withAdResumePositionUs(
|
adPlaybackState.withAdResumePositionUs(
|
||||||
|
|
@ -585,6 +625,7 @@ public final class ImaAdsLoader
|
||||||
lastVolumePercentage = getVolume();
|
lastVolumePercentage = getVolume();
|
||||||
lastAdProgress = getAdProgress();
|
lastAdProgress = getAdProgress();
|
||||||
lastContentProgress = getContentProgress();
|
lastContentProgress = getContentProgress();
|
||||||
|
adDisplayContainer.unregisterAllVideoControlsOverlays();
|
||||||
player.removeListener(this);
|
player.removeListener(this);
|
||||||
player = null;
|
player = null;
|
||||||
eventListener = null;
|
eventListener = null;
|
||||||
|
|
@ -1331,7 +1372,8 @@ public final class ImaAdsLoader
|
||||||
private static boolean isAdGroupLoadError(AdError adError) {
|
private static boolean isAdGroupLoadError(AdError adError) {
|
||||||
// TODO: Find out what other errors need to be handled (if any), and whether each one relates to
|
// TODO: Find out what other errors need to be handled (if any), and whether each one relates to
|
||||||
// a single ad, ad group or the whole timeline.
|
// a single ad, ad group or the whole timeline.
|
||||||
return adError.getErrorCode() == AdErrorCode.VAST_LINEAR_ASSET_MISMATCH;
|
return adError.getErrorCode() == AdErrorCode.VAST_LINEAR_ASSET_MISMATCH
|
||||||
|
|| adError.getErrorCode() == AdErrorCode.UNKNOWN_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) {
|
private static boolean hasMidrollAdGroups(long[] adGroupTimesUs) {
|
||||||
|
|
@ -1357,9 +1399,9 @@ public final class ImaAdsLoader
|
||||||
AdDisplayContainer createAdDisplayContainer();
|
AdDisplayContainer createAdDisplayContainer();
|
||||||
/** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */
|
/** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */
|
||||||
AdsRequest createAdsRequest();
|
AdsRequest createAdsRequest();
|
||||||
/** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings) */
|
/** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */
|
||||||
com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
||||||
Context context, ImaSdkSettings imaSdkSettings);
|
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */
|
/** Default {@link ImaFactory} for non-test usage, which delegates to {@link ImaSdkFactory}. */
|
||||||
|
|
@ -1386,8 +1428,9 @@ public final class ImaAdsLoader
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
||||||
Context context, ImaSdkSettings imaSdkSettings) {
|
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
|
||||||
return ImaSdkFactory.getInstance().createAdsLoader(context, imaSdkSettings);
|
return ImaSdkFactory.getInstance()
|
||||||
|
.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2017 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer2.ext.ima;
|
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
|
||||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource.SourceInfoRefreshListener;
|
|
||||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link MediaSource} that inserts ads linearly with a provided content media source.
|
|
||||||
*
|
|
||||||
* @deprecated Use com.google.android.exoplayer2.source.ads.AdsMediaSource with ImaAdsLoader.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public final class ImaAdsMediaSource extends BaseMediaSource implements SourceInfoRefreshListener {
|
|
||||||
|
|
||||||
private final AdsMediaSource adsMediaSource;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new source that inserts ads linearly with the content specified by
|
|
||||||
* {@code contentMediaSource}.
|
|
||||||
*
|
|
||||||
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
|
||||||
* @param dataSourceFactory Factory for data sources used to load ad media.
|
|
||||||
* @param imaAdsLoader The loader for ads.
|
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
|
||||||
*/
|
|
||||||
public ImaAdsMediaSource(MediaSource contentMediaSource, DataSource.Factory dataSourceFactory,
|
|
||||||
ImaAdsLoader imaAdsLoader, ViewGroup adUiViewGroup) {
|
|
||||||
this(contentMediaSource, dataSourceFactory, imaAdsLoader, adUiViewGroup, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new source that inserts ads linearly with the content specified by {@code
|
|
||||||
* contentMediaSource}.
|
|
||||||
*
|
|
||||||
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
|
||||||
* @param dataSourceFactory Factory for data sources used to load ad media.
|
|
||||||
* @param imaAdsLoader The loader for ads.
|
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
|
||||||
* @param eventHandler A handler for events. May be null if delivery of events is not required.
|
|
||||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
|
||||||
*/
|
|
||||||
public ImaAdsMediaSource(
|
|
||||||
MediaSource contentMediaSource,
|
|
||||||
DataSource.Factory dataSourceFactory,
|
|
||||||
ImaAdsLoader imaAdsLoader,
|
|
||||||
ViewGroup adUiViewGroup,
|
|
||||||
@Nullable Handler eventHandler,
|
|
||||||
@Nullable AdsMediaSource.EventListener eventListener) {
|
|
||||||
adsMediaSource = new AdsMediaSource(contentMediaSource, dataSourceFactory, imaAdsLoader,
|
|
||||||
adUiViewGroup, eventHandler, eventListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Nullable
|
|
||||||
public Object getTag() {
|
|
||||||
return adsMediaSource.getTag();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void prepareSourceInternal(
|
|
||||||
final ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
adsMediaSource.prepareSource(
|
|
||||||
player, isTopLevelSource, /* listener= */ this, mediaTransferListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
|
||||||
adsMediaSource.maybeThrowSourceInfoRefreshError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
|
||||||
return adsMediaSource.createPeriod(id, allocator, startPositionUs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
|
||||||
adsMediaSource.releasePeriod(mediaPeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void releaseSourceInternal() {
|
|
||||||
adsMediaSource.releaseSource(/* listener= */ this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSourceInfoRefreshed(
|
|
||||||
MediaSource source, Timeline timeline, @Nullable Object manifest) {
|
|
||||||
refreshSourceInfo(timeline, manifest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.testutil.StubExoPlayer;
|
import com.google.android.exoplayer2.testutil.StubExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/** A fake player for testing content/ad playback. */
|
/** A fake player for testing content/ad playback. */
|
||||||
|
|
@ -109,6 +110,11 @@ import java.util.ArrayList;
|
||||||
|
|
||||||
// ExoPlayer methods. Other methods are unsupported.
|
// ExoPlayer methods. Other methods are unsupported.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AudioComponent getAudioComponent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Looper getApplicationLooper() {
|
public Looper getApplicationLooper() {
|
||||||
return Looper.getMainLooper();
|
return Looper.getMainLooper();
|
||||||
|
|
@ -134,6 +140,16 @@ import java.util.ArrayList;
|
||||||
return playWhenReady;
|
return playWhenReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRendererCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackSelectionArray getCurrentTrackSelections() {
|
||||||
|
return new TrackSelectionArray();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Timeline getCurrentTimeline() {
|
public Timeline getCurrentTimeline() {
|
||||||
return timeline;
|
return timeline;
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@ package com.google.android.exoplayer2.ext.ima;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||||
|
|
@ -49,6 +51,7 @@ import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
|
@ -73,7 +76,9 @@ public class ImaAdsLoaderTest {
|
||||||
private @Mock AdDisplayContainer adDisplayContainer;
|
private @Mock AdDisplayContainer adDisplayContainer;
|
||||||
private @Mock AdsManager adsManager;
|
private @Mock AdsManager adsManager;
|
||||||
private SingletonImaFactory testImaFactory;
|
private SingletonImaFactory testImaFactory;
|
||||||
private ViewGroup adUiViewGroup;
|
private ViewGroup adViewGroup;
|
||||||
|
private View adOverlayView;
|
||||||
|
private AdsLoader.AdViewProvider adViewProvider;
|
||||||
private TestAdsLoaderListener adsLoaderListener;
|
private TestAdsLoaderListener adsLoaderListener;
|
||||||
private FakePlayer fakeExoPlayer;
|
private FakePlayer fakeExoPlayer;
|
||||||
private ImaAdsLoader imaAdsLoader;
|
private ImaAdsLoader imaAdsLoader;
|
||||||
|
|
@ -90,7 +95,20 @@ public class ImaAdsLoaderTest {
|
||||||
adDisplayContainer,
|
adDisplayContainer,
|
||||||
fakeAdsRequest,
|
fakeAdsRequest,
|
||||||
fakeAdsLoader);
|
fakeAdsLoader);
|
||||||
adUiViewGroup = new FrameLayout(RuntimeEnvironment.application);
|
adViewGroup = new FrameLayout(RuntimeEnvironment.application);
|
||||||
|
adOverlayView = new View(RuntimeEnvironment.application);
|
||||||
|
adViewProvider =
|
||||||
|
new AdsLoader.AdViewProvider() {
|
||||||
|
@Override
|
||||||
|
public ViewGroup getAdViewGroup() {
|
||||||
|
return adViewGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View[] getAdOverlayViews() {
|
||||||
|
return new View[] {adOverlayView};
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|
@ -109,17 +127,18 @@ public class ImaAdsLoaderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAttachPlayer_setsAdUiViewGroup() {
|
public void testStart_setsAdUiViewGroup() {
|
||||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
verify(adDisplayContainer, atLeastOnce()).setAdContainer(adUiViewGroup);
|
verify(adDisplayContainer, atLeastOnce()).setAdContainer(adViewGroup);
|
||||||
|
verify(adDisplayContainer, atLeastOnce()).registerVideoControlsOverlay(adOverlayView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAttachPlayer_updatesAdPlaybackState() {
|
public void testStart_updatesAdPlaybackState() {
|
||||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
|
||||||
assertThat(adsLoaderListener.adPlaybackState)
|
assertThat(adsLoaderListener.adPlaybackState)
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
|
|
@ -128,17 +147,17 @@ public class ImaAdsLoaderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAttachAfterRelease() {
|
public void testStartAfterRelease() {
|
||||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||||
imaAdsLoader.release();
|
imaAdsLoader.release();
|
||||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAttachAndCallbacksAfterRelease() {
|
public void testStartAndCallbacksAfterRelease() {
|
||||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||||
imaAdsLoader.release();
|
imaAdsLoader.release();
|
||||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
|
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
|
||||||
fakeExoPlayer.setState(Player.STATE_READY, true);
|
fakeExoPlayer.setState(Player.STATE_READY, true);
|
||||||
|
|
||||||
|
|
@ -146,7 +165,7 @@ public class ImaAdsLoaderTest {
|
||||||
// Note: we can't currently call getContentProgress/getAdProgress as a VerifyError is thrown
|
// Note: we can't currently call getContentProgress/getAdProgress as a VerifyError is thrown
|
||||||
// when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA
|
// when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA
|
||||||
// SDK being proguarded.
|
// SDK being proguarded.
|
||||||
imaAdsLoader.requestAds(adUiViewGroup);
|
imaAdsLoader.requestAds(adViewGroup);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
|
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
|
||||||
imaAdsLoader.loadAd(TEST_URI.toString());
|
imaAdsLoader.loadAd(TEST_URI.toString());
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
|
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
|
||||||
|
|
@ -166,7 +185,7 @@ public class ImaAdsLoaderTest {
|
||||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||||
|
|
||||||
// Load the preroll ad.
|
// Load the preroll ad.
|
||||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
|
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
|
||||||
imaAdsLoader.loadAd(TEST_URI.toString());
|
imaAdsLoader.loadAd(TEST_URI.toString());
|
||||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
|
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
|
||||||
|
|
@ -201,6 +220,18 @@ public class ImaAdsLoaderTest {
|
||||||
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
|
.withAdResumePositionUs(/* adResumePositionUs= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStop_unregistersAllVideoControlOverlays() {
|
||||||
|
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||||
|
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||||
|
imaAdsLoader.requestAds(adViewGroup);
|
||||||
|
imaAdsLoader.stop();
|
||||||
|
|
||||||
|
InOrder inOrder = inOrder(adDisplayContainer);
|
||||||
|
inOrder.verify(adDisplayContainer).registerVideoControlsOverlay(adOverlayView);
|
||||||
|
inOrder.verify(adDisplayContainer).unregisterAllVideoControlsOverlays();
|
||||||
|
}
|
||||||
|
|
||||||
private void setupPlayback(Timeline contentTimeline, long[][] adDurationsUs, Float[] cuePoints) {
|
private void setupPlayback(Timeline contentTimeline, long[][] adDurationsUs, Float[] cuePoints) {
|
||||||
fakeExoPlayer = new FakePlayer();
|
fakeExoPlayer = new FakePlayer();
|
||||||
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs);
|
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs);
|
||||||
|
|
@ -210,6 +241,7 @@ public class ImaAdsLoaderTest {
|
||||||
.setImaFactory(testImaFactory)
|
.setImaFactory(testImaFactory)
|
||||||
.setImaSdkSettings(imaSdkSettings)
|
.setImaSdkSettings(imaSdkSettings)
|
||||||
.buildForAdTag(TEST_URI);
|
.buildForAdTag(TEST_URI);
|
||||||
|
imaAdsLoader.setPlayer(fakeExoPlayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AdEvent getAdEvent(AdEventType adEventType, @Nullable Ad ad) {
|
private static AdEvent getAdEvent(AdEventType adEventType, @Nullable Ad ad) {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.ima;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
|
import com.google.ads.interactivemedia.v3.api.AdDisplayContainer;
|
||||||
|
import com.google.ads.interactivemedia.v3.api.AdsLoader;
|
||||||
import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
|
import com.google.ads.interactivemedia.v3.api.AdsRenderingSettings;
|
||||||
import com.google.ads.interactivemedia.v3.api.AdsRequest;
|
import com.google.ads.interactivemedia.v3.api.AdsRequest;
|
||||||
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
||||||
|
|
@ -64,8 +65,8 @@ final class SingletonImaFactory implements ImaAdsLoader.ImaFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
public AdsLoader createAdsLoader(
|
||||||
Context context, ImaSdkSettings imaSdkSettings) {
|
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
|
||||||
return adsLoader;
|
return adsLoader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
repeatMode,
|
repeatMode,
|
||||||
shuffleModeEnabled,
|
shuffleModeEnabled,
|
||||||
eventHandler,
|
eventHandler,
|
||||||
this,
|
|
||||||
clock);
|
clock);
|
||||||
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
|
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,6 @@ import java.util.Collections;
|
||||||
private final HandlerWrapper handler;
|
private final HandlerWrapper handler;
|
||||||
private final HandlerThread internalPlaybackThread;
|
private final HandlerThread internalPlaybackThread;
|
||||||
private final Handler eventHandler;
|
private final Handler eventHandler;
|
||||||
private final ExoPlayer player;
|
|
||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
private final Timeline.Period period;
|
private final Timeline.Period period;
|
||||||
private final long backBufferDurationUs;
|
private final long backBufferDurationUs;
|
||||||
|
|
@ -131,7 +130,6 @@ import java.util.Collections;
|
||||||
@Player.RepeatMode int repeatMode,
|
@Player.RepeatMode int repeatMode,
|
||||||
boolean shuffleModeEnabled,
|
boolean shuffleModeEnabled,
|
||||||
Handler eventHandler,
|
Handler eventHandler,
|
||||||
ExoPlayer player,
|
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
|
|
@ -142,7 +140,6 @@ import java.util.Collections;
|
||||||
this.repeatMode = repeatMode;
|
this.repeatMode = repeatMode;
|
||||||
this.shuffleModeEnabled = shuffleModeEnabled;
|
this.shuffleModeEnabled = shuffleModeEnabled;
|
||||||
this.eventHandler = eventHandler;
|
this.eventHandler = eventHandler;
|
||||||
this.player = player;
|
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.queue = new MediaPeriodQueue();
|
this.queue = new MediaPeriodQueue();
|
||||||
|
|
||||||
|
|
@ -398,11 +395,7 @@ import java.util.Collections;
|
||||||
loadControl.onPrepared();
|
loadControl.onPrepared();
|
||||||
this.mediaSource = mediaSource;
|
this.mediaSource = mediaSource;
|
||||||
setState(Player.STATE_BUFFERING);
|
setState(Player.STATE_BUFFERING);
|
||||||
mediaSource.prepareSource(
|
mediaSource.prepareSource(/* listener= */ this, bandwidthMeter.getTransferListener());
|
||||||
player,
|
|
||||||
/* isTopLevelSource= */ true,
|
|
||||||
/* listener= */ this,
|
|
||||||
bandwidthMeter.getTransferListener());
|
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
|
||||||
|
|
||||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||||
public static final String VERSION = "2.9.5";
|
public static final String VERSION = "2.9.6";
|
||||||
|
|
||||||
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.5";
|
public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.6";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003.
|
* The version of the library expressed as an integer, for example 1002003.
|
||||||
|
|
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
|
||||||
* integer version 123045006 (123-045-006).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final int VERSION_INT = 2009005;
|
public static final int VERSION_INT = 2009006;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||||
|
|
|
||||||
|
|
@ -591,6 +591,14 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||||
long presentationTimeDeltaUs =
|
long presentationTimeDeltaUs =
|
||||||
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
|
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
|
||||||
|
|
||||||
|
// The presentation_time_delta is accounted for by adjusting the sample timestamp, so we zero it
|
||||||
|
// in the sample data before writing it to the track outputs.
|
||||||
|
int position = atom.getPosition();
|
||||||
|
atom.data[position - 4] = 0;
|
||||||
|
atom.data[position - 3] = 0;
|
||||||
|
atom.data[position - 2] = 0;
|
||||||
|
atom.data[position - 1] = 0;
|
||||||
|
|
||||||
// Output the sample data.
|
// Output the sample data.
|
||||||
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
|
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
|
||||||
atom.setPosition(Atom.FULL_HEADER_SIZE);
|
atom.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
|
|
|
||||||
|
|
@ -44,13 +44,6 @@ public final class EventMessage implements Metadata.Entry {
|
||||||
*/
|
*/
|
||||||
public final long durationMs;
|
public final long durationMs;
|
||||||
|
|
||||||
/**
|
|
||||||
* The presentation time value of this event message in microseconds.
|
|
||||||
* <p>
|
|
||||||
* Except in special cases, application code should <em>not</em> use this field.
|
|
||||||
*/
|
|
||||||
public final long presentationTimeUs;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The instance identifier.
|
* The instance identifier.
|
||||||
*/
|
*/
|
||||||
|
|
@ -70,22 +63,19 @@ public final class EventMessage implements Metadata.Entry {
|
||||||
* @param durationMs The duration of the event in milliseconds.
|
* @param durationMs The duration of the event in milliseconds.
|
||||||
* @param id The instance identifier.
|
* @param id The instance identifier.
|
||||||
* @param messageData The body of the message.
|
* @param messageData The body of the message.
|
||||||
* @param presentationTimeUs The presentation time value of this event message in microseconds.
|
|
||||||
*/
|
*/
|
||||||
public EventMessage(String schemeIdUri, String value, long durationMs, long id,
|
public EventMessage(
|
||||||
byte[] messageData, long presentationTimeUs) {
|
String schemeIdUri, String value, long durationMs, long id, byte[] messageData) {
|
||||||
this.schemeIdUri = schemeIdUri;
|
this.schemeIdUri = schemeIdUri;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.durationMs = durationMs;
|
this.durationMs = durationMs;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.messageData = messageData;
|
this.messageData = messageData;
|
||||||
this.presentationTimeUs = presentationTimeUs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ EventMessage(Parcel in) {
|
/* package */ EventMessage(Parcel in) {
|
||||||
schemeIdUri = castNonNull(in.readString());
|
schemeIdUri = castNonNull(in.readString());
|
||||||
value = castNonNull(in.readString());
|
value = castNonNull(in.readString());
|
||||||
presentationTimeUs = in.readLong();
|
|
||||||
durationMs = in.readLong();
|
durationMs = in.readLong();
|
||||||
id = in.readLong();
|
id = in.readLong();
|
||||||
messageData = castNonNull(in.createByteArray());
|
messageData = castNonNull(in.createByteArray());
|
||||||
|
|
@ -97,7 +87,6 @@ public final class EventMessage implements Metadata.Entry {
|
||||||
int result = 17;
|
int result = 17;
|
||||||
result = 31 * result + (schemeIdUri != null ? schemeIdUri.hashCode() : 0);
|
result = 31 * result + (schemeIdUri != null ? schemeIdUri.hashCode() : 0);
|
||||||
result = 31 * result + (value != null ? value.hashCode() : 0);
|
result = 31 * result + (value != null ? value.hashCode() : 0);
|
||||||
result = 31 * result + (int) (presentationTimeUs ^ (presentationTimeUs >>> 32));
|
|
||||||
result = 31 * result + (int) (durationMs ^ (durationMs >>> 32));
|
result = 31 * result + (int) (durationMs ^ (durationMs >>> 32));
|
||||||
result = 31 * result + (int) (id ^ (id >>> 32));
|
result = 31 * result + (int) (id ^ (id >>> 32));
|
||||||
result = 31 * result + Arrays.hashCode(messageData);
|
result = 31 * result + Arrays.hashCode(messageData);
|
||||||
|
|
@ -115,9 +104,11 @@ public final class EventMessage implements Metadata.Entry {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EventMessage other = (EventMessage) obj;
|
EventMessage other = (EventMessage) obj;
|
||||||
return presentationTimeUs == other.presentationTimeUs && durationMs == other.durationMs
|
return durationMs == other.durationMs
|
||||||
&& id == other.id && Util.areEqual(schemeIdUri, other.schemeIdUri)
|
&& id == other.id
|
||||||
&& Util.areEqual(value, other.value) && Arrays.equals(messageData, other.messageData);
|
&& Util.areEqual(schemeIdUri, other.schemeIdUri)
|
||||||
|
&& Util.areEqual(value, other.value)
|
||||||
|
&& Arrays.equals(messageData, other.messageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -136,7 +127,6 @@ public final class EventMessage implements Metadata.Entry {
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeString(schemeIdUri);
|
dest.writeString(schemeIdUri);
|
||||||
dest.writeString(value);
|
dest.writeString(value);
|
||||||
dest.writeLong(presentationTimeUs);
|
|
||||||
dest.writeLong(durationMs);
|
dest.writeLong(durationMs);
|
||||||
dest.writeLong(id);
|
dest.writeLong(id);
|
||||||
dest.writeByteArray(messageData);
|
dest.writeByteArray(messageData);
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.metadata.emsg;
|
package com.google.android.exoplayer2.metadata.emsg;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.metadata.MetadataDecoder;
|
import com.google.android.exoplayer2.metadata.MetadataDecoder;
|
||||||
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
@ -27,12 +27,15 @@ import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes Event Message (emsg) atoms, as defined in ISO/IEC 23009-1:2014, Section 5.10.3.3.
|
* Decodes Event Message (emsg) atoms, as defined in ISO/IEC 23009-1:2014, Section 5.10.3.3.
|
||||||
* <p>
|
*
|
||||||
* Atom data should be provided to the decoder without the full atom header (i.e. starting from the
|
* <p>Atom data should be provided to the decoder without the full atom header (i.e. starting from
|
||||||
* first byte of the scheme_id_uri field).
|
* the first byte of the scheme_id_uri field). It is expected that the presentation_time_delta field
|
||||||
|
* should be 0, having already been accounted for by adjusting the sample timestamp.
|
||||||
*/
|
*/
|
||||||
public final class EventMessageDecoder implements MetadataDecoder {
|
public final class EventMessageDecoder implements MetadataDecoder {
|
||||||
|
|
||||||
|
private static final String TAG = "EventMessageDecoder";
|
||||||
|
|
||||||
@SuppressWarnings("ByteBufferBackingArray")
|
@SuppressWarnings("ByteBufferBackingArray")
|
||||||
@Override
|
@Override
|
||||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||||
|
|
@ -43,13 +46,16 @@ public final class EventMessageDecoder implements MetadataDecoder {
|
||||||
String schemeIdUri = Assertions.checkNotNull(emsgData.readNullTerminatedString());
|
String schemeIdUri = Assertions.checkNotNull(emsgData.readNullTerminatedString());
|
||||||
String value = Assertions.checkNotNull(emsgData.readNullTerminatedString());
|
String value = Assertions.checkNotNull(emsgData.readNullTerminatedString());
|
||||||
long timescale = emsgData.readUnsignedInt();
|
long timescale = emsgData.readUnsignedInt();
|
||||||
long presentationTimeUs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(),
|
long presentationTimeDelta = emsgData.readUnsignedInt();
|
||||||
C.MICROS_PER_SECOND, timescale);
|
if (presentationTimeDelta != 0) {
|
||||||
|
// We expect the source to have accounted for presentation_time_delta by adjusting the sample
|
||||||
|
// timestamp and zeroing the field in the sample data. Log a warning if the field is non-zero.
|
||||||
|
Log.w(TAG, "Ignoring non-zero presentation_time_delta: " + presentationTimeDelta);
|
||||||
|
}
|
||||||
long durationMs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(), 1000, timescale);
|
long durationMs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(), 1000, timescale);
|
||||||
long id = emsgData.readUnsignedInt();
|
long id = emsgData.readUnsignedInt();
|
||||||
byte[] messageData = Arrays.copyOfRange(data, emsgData.getPosition(), size);
|
byte[] messageData = Arrays.copyOfRange(data, emsgData.getPosition(), size);
|
||||||
return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData,
|
return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData));
|
||||||
presentationTimeUs));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,6 @@
|
||||||
package com.google.android.exoplayer2.metadata.emsg;
|
package com.google.android.exoplayer2.metadata.emsg;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -37,27 +34,22 @@ public final class EventMessageEncoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes an {@link EventMessage} to a byte array that can be decoded by
|
* Encodes an {@link EventMessage} to a byte array that can be decoded by {@link
|
||||||
* {@link EventMessageDecoder}.
|
* EventMessageDecoder}.
|
||||||
*
|
*
|
||||||
* @param eventMessage The event message to be encoded.
|
* @param eventMessage The event message to be encoded.
|
||||||
* @param timescale Timescale of the event message, in units per second.
|
|
||||||
* @return The serialized byte array.
|
* @return The serialized byte array.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public byte[] encode(EventMessage eventMessage, long timescale) {
|
public byte[] encode(EventMessage eventMessage) {
|
||||||
Assertions.checkArgument(timescale >= 0);
|
|
||||||
byteArrayOutputStream.reset();
|
byteArrayOutputStream.reset();
|
||||||
try {
|
try {
|
||||||
writeNullTerminatedString(dataOutputStream, eventMessage.schemeIdUri);
|
writeNullTerminatedString(dataOutputStream, eventMessage.schemeIdUri);
|
||||||
String nonNullValue = eventMessage.value != null ? eventMessage.value : "";
|
String nonNullValue = eventMessage.value != null ? eventMessage.value : "";
|
||||||
writeNullTerminatedString(dataOutputStream, nonNullValue);
|
writeNullTerminatedString(dataOutputStream, nonNullValue);
|
||||||
writeUnsignedInt(dataOutputStream, timescale);
|
writeUnsignedInt(dataOutputStream, 1000); // timescale
|
||||||
long presentationTime = Util.scaleLargeTimestamp(eventMessage.presentationTimeUs,
|
writeUnsignedInt(dataOutputStream, 0); // presentation_time_delta
|
||||||
timescale, C.MICROS_PER_SECOND);
|
writeUnsignedInt(dataOutputStream, eventMessage.durationMs);
|
||||||
writeUnsignedInt(dataOutputStream, presentationTime);
|
|
||||||
long duration = Util.scaleLargeTimestamp(eventMessage.durationMs, timescale, 1000);
|
|
||||||
writeUnsignedInt(dataOutputStream, duration);
|
|
||||||
writeUnsignedInt(dataOutputStream, eventMessage.id);
|
writeUnsignedInt(dataOutputStream, eventMessage.id);
|
||||||
dataOutputStream.write(eventMessage.messageData);
|
dataOutputStream.write(eventMessage.messageData);
|
||||||
dataOutputStream.flush();
|
dataOutputStream.flush();
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -35,9 +35,9 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||||
private final ArrayList<SourceInfoRefreshListener> sourceInfoListeners;
|
private final ArrayList<SourceInfoRefreshListener> sourceInfoListeners;
|
||||||
private final MediaSourceEventListener.EventDispatcher eventDispatcher;
|
private final MediaSourceEventListener.EventDispatcher eventDispatcher;
|
||||||
|
|
||||||
private @Nullable ExoPlayer player;
|
@Nullable private Looper looper;
|
||||||
private @Nullable Timeline timeline;
|
@Nullable private Timeline timeline;
|
||||||
private @Nullable Object manifest;
|
@Nullable private Object manifest;
|
||||||
|
|
||||||
public BaseMediaSource() {
|
public BaseMediaSource() {
|
||||||
sourceInfoListeners = new ArrayList<>(/* initialCapacity= */ 1);
|
sourceInfoListeners = new ArrayList<>(/* initialCapacity= */ 1);
|
||||||
|
|
@ -48,21 +48,16 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||||
* Starts source preparation. This method is called at most once until the next call to {@link
|
* Starts source preparation. This method is called at most once until the next call to {@link
|
||||||
* #releaseSourceInternal()}.
|
* #releaseSourceInternal()}.
|
||||||
*
|
*
|
||||||
* @param player The player for which this source is being prepared.
|
|
||||||
* @param isTopLevelSource Whether this source has been passed directly to {@link
|
|
||||||
* ExoPlayer#prepare(MediaSource)} or {@link ExoPlayer#prepare(MediaSource, boolean,
|
|
||||||
* boolean)}.
|
|
||||||
* @param mediaTransferListener The transfer listener which should be informed of any media data
|
* @param mediaTransferListener The transfer listener which should be informed of any media data
|
||||||
* transfers. May be null if no listener is available. Note that this listener should usually
|
* transfers. May be null if no listener is available. Note that this listener should usually
|
||||||
* be only informed of transfers related to the media loads and not of auxiliary loads for
|
* be only informed of transfers related to the media loads and not of auxiliary loads for
|
||||||
* manifests and other data.
|
* manifests and other data.
|
||||||
*/
|
*/
|
||||||
protected abstract void prepareSourceInternal(
|
protected abstract void prepareSourceInternal(@Nullable TransferListener mediaTransferListener);
|
||||||
ExoPlayer player, boolean isTopLevelSource, @Nullable TransferListener mediaTransferListener);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the source. This method is called exactly once after each call to {@link
|
* Releases the source. This method is called exactly once after each call to {@link
|
||||||
* #prepareSourceInternal(ExoPlayer, boolean, TransferListener)}.
|
* #prepareSourceInternal(TransferListener)}.
|
||||||
*/
|
*/
|
||||||
protected abstract void releaseSourceInternal();
|
protected abstract void releaseSourceInternal();
|
||||||
|
|
||||||
|
|
@ -135,21 +130,14 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void prepareSource(
|
public final void prepareSource(
|
||||||
ExoPlayer player, boolean isTopLevelSource, SourceInfoRefreshListener listener) {
|
|
||||||
prepareSource(player, isTopLevelSource, listener, /* mediaTransferListener= */ null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void prepareSource(
|
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
SourceInfoRefreshListener listener,
|
SourceInfoRefreshListener listener,
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
@Nullable TransferListener mediaTransferListener) {
|
||||||
Assertions.checkArgument(this.player == null || this.player == player);
|
Looper looper = Looper.myLooper();
|
||||||
|
Assertions.checkArgument(this.looper == null || this.looper == looper);
|
||||||
sourceInfoListeners.add(listener);
|
sourceInfoListeners.add(listener);
|
||||||
if (this.player == null) {
|
if (this.looper == null) {
|
||||||
this.player = player;
|
this.looper = looper;
|
||||||
prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
prepareSourceInternal(mediaTransferListener);
|
||||||
} else if (timeline != null) {
|
} else if (timeline != null) {
|
||||||
listener.onSourceInfoRefreshed(/* source= */ this, timeline, manifest);
|
listener.onSourceInfoRefreshed(/* source= */ this, timeline, manifest);
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +147,7 @@ public abstract class BaseMediaSource implements MediaSource {
|
||||||
public final void releaseSource(SourceInfoRefreshListener listener) {
|
public final void releaseSource(SourceInfoRefreshListener listener) {
|
||||||
sourceInfoListeners.remove(listener);
|
sourceInfoListeners.remove(listener);
|
||||||
if (sourceInfoListeners.isEmpty()) {
|
if (sourceInfoListeners.isEmpty()) {
|
||||||
player = null;
|
looper = null;
|
||||||
timeline = null;
|
timeline = null;
|
||||||
manifest = null;
|
manifest = null;
|
||||||
releaseSourceInternal();
|
releaseSourceInternal();
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
|
|
@ -223,11 +222,8 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
|
||||||
prepareChildSource(/* id= */ null, mediaSource);
|
prepareChildSource(/* id= */ null, mediaSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.CallSuper;
|
import android.support.annotation.CallSuper;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -35,7 +34,6 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
||||||
|
|
||||||
private final HashMap<T, MediaSourceAndListener> childSources;
|
private final HashMap<T, MediaSourceAndListener> childSources;
|
||||||
|
|
||||||
private @Nullable ExoPlayer player;
|
|
||||||
private @Nullable Handler eventHandler;
|
private @Nullable Handler eventHandler;
|
||||||
private @Nullable TransferListener mediaTransferListener;
|
private @Nullable TransferListener mediaTransferListener;
|
||||||
|
|
||||||
|
|
@ -46,11 +44,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
this.player = player;
|
|
||||||
this.mediaTransferListener = mediaTransferListener;
|
this.mediaTransferListener = mediaTransferListener;
|
||||||
eventHandler = new Handler();
|
eventHandler = new Handler();
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +65,6 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
||||||
childSource.mediaSource.removeEventListener(childSource.eventListener);
|
childSource.mediaSource.removeEventListener(childSource.eventListener);
|
||||||
}
|
}
|
||||||
childSources.clear();
|
childSources.clear();
|
||||||
player = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -105,11 +98,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
||||||
MediaSourceEventListener eventListener = new ForwardingEventListener(id);
|
MediaSourceEventListener eventListener = new ForwardingEventListener(id);
|
||||||
childSources.put(id, new MediaSourceAndListener(mediaSource, sourceListener, eventListener));
|
childSources.put(id, new MediaSourceAndListener(mediaSource, sourceListener, eventListener));
|
||||||
mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener);
|
mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener);
|
||||||
mediaSource.prepareSource(
|
mediaSource.prepareSource(sourceListener, mediaTransferListener);
|
||||||
Assertions.checkNotNull(player),
|
|
||||||
/* isTopLevelSource= */ false,
|
|
||||||
sourceListener,
|
|
||||||
mediaTransferListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder;
|
import com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder;
|
||||||
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
|
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
|
||||||
|
|
@ -428,10 +427,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final synchronized void prepareSourceInternal(
|
public final synchronized void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
@Nullable TransferListener mediaTransferListener) {
|
||||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
|
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
|
||||||
if (mediaSourcesPublic.isEmpty()) {
|
if (mediaSourcesPublic.isEmpty()) {
|
||||||
updateTimelineAndScheduleOnCompletionActions();
|
updateTimelineAndScheduleOnCompletionActions();
|
||||||
|
|
@ -1163,10 +1160,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
||||||
private static final class DummyMediaSource extends BaseMediaSource {
|
private static final class DummyMediaSource extends BaseMediaSource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void prepareSourceInternal(
|
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
|
|
@ -365,10 +364,7 @@ public final class ExtractorMediaSource extends BaseMediaSource
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
transferListener = mediaTransferListener;
|
transferListener = mediaTransferListener;
|
||||||
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable);
|
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,8 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
|
||||||
prepareChildSource(/* id= */ null, childSource);
|
prepareChildSource(/* id= */ null, childSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,23 +25,24 @@ import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines and provides media to be played by an {@link ExoPlayer}. A MediaSource has two main
|
* Defines and provides media to be played by an {@link com.google.android.exoplayer2.ExoPlayer}. A
|
||||||
* responsibilities:
|
* MediaSource has two main responsibilities:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>To provide the player with a {@link Timeline} defining the structure of its media, and to
|
* <li>To provide the player with a {@link Timeline} defining the structure of its media, and to
|
||||||
* provide a new timeline whenever the structure of the media changes. The MediaSource
|
* provide a new timeline whenever the structure of the media changes. The MediaSource
|
||||||
* provides these timelines by calling {@link SourceInfoRefreshListener#onSourceInfoRefreshed}
|
* provides these timelines by calling {@link SourceInfoRefreshListener#onSourceInfoRefreshed}
|
||||||
* on the {@link SourceInfoRefreshListener}s passed to {@link #prepareSource(ExoPlayer,
|
* on the {@link SourceInfoRefreshListener}s passed to {@link
|
||||||
* boolean, SourceInfoRefreshListener, TransferListener)}.
|
* #prepareSource(SourceInfoRefreshListener, TransferListener)}.
|
||||||
* <li>To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are
|
* <li>To provide {@link MediaPeriod} instances for the periods in its timeline. MediaPeriods are
|
||||||
* obtained by calling {@link #createPeriod(MediaPeriodId, Allocator, long)}, and provide a
|
* obtained by calling {@link #createPeriod(MediaPeriodId, Allocator, long)}, and provide a
|
||||||
* way for the player to load and read the media.
|
* way for the player to load and read the media.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* All methods are called on the player's internal playback thread, as described in the {@link
|
* All methods are called on the player's internal playback thread, as described in the {@link
|
||||||
* ExoPlayer} Javadoc. They should not be called directly from application code. Instances can be
|
* com.google.android.exoplayer2.ExoPlayer} Javadoc. They should not be called directly from
|
||||||
* re-used, but only for one {@link ExoPlayer} instance simultaneously.
|
* application code. Instances can be re-used, but only for one {@link
|
||||||
|
* com.google.android.exoplayer2.ExoPlayer} instance simultaneously.
|
||||||
*/
|
*/
|
||||||
public interface MediaSource {
|
public interface MediaSource {
|
||||||
|
|
||||||
|
|
@ -226,11 +227,6 @@ public interface MediaSource {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Will be removed in the next release. */
|
|
||||||
@Deprecated
|
|
||||||
void prepareSource(
|
|
||||||
ExoPlayer player, boolean isTopLevelSource, SourceInfoRefreshListener listener);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts source preparation if not yet started, and adds a listener for timeline and/or manifest
|
* Starts source preparation if not yet started, and adds a listener for timeline and/or manifest
|
||||||
* updates.
|
* updates.
|
||||||
|
|
@ -242,11 +238,6 @@ public interface MediaSource {
|
||||||
* <p>For each call to this method, a call to {@link #releaseSource(SourceInfoRefreshListener)} is
|
* <p>For each call to this method, a call to {@link #releaseSource(SourceInfoRefreshListener)} is
|
||||||
* needed to remove the listener and to release the source if no longer required.
|
* needed to remove the listener and to release the source if no longer required.
|
||||||
*
|
*
|
||||||
* @param player The player for which this source is being prepared.
|
|
||||||
* @param isTopLevelSource Whether this source has been passed directly to {@link
|
|
||||||
* ExoPlayer#prepare(MediaSource)} or {@link ExoPlayer#prepare(MediaSource, boolean,
|
|
||||||
* boolean)}. If {@code false}, this source is being prepared by another source (e.g. {@link
|
|
||||||
* ConcatenatingMediaSource}) for composition.
|
|
||||||
* @param listener The listener to be added.
|
* @param listener The listener to be added.
|
||||||
* @param mediaTransferListener The transfer listener which should be informed of any media data
|
* @param mediaTransferListener The transfer listener which should be informed of any media data
|
||||||
* transfers. May be null if no listener is available. Note that this listener should be only
|
* transfers. May be null if no listener is available. Note that this listener should be only
|
||||||
|
|
@ -254,10 +245,7 @@ public interface MediaSource {
|
||||||
* and other data.
|
* and other data.
|
||||||
*/
|
*/
|
||||||
void prepareSource(
|
void prepareSource(
|
||||||
ExoPlayer player,
|
SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener);
|
||||||
boolean isTopLevelSource,
|
|
||||||
SourceInfoRefreshListener listener,
|
|
||||||
@Nullable TransferListener mediaTransferListener);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws any pending error encountered while loading or refreshing source information.
|
* Throws any pending error encountered while loading or refreshing source information.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
|
|
@ -105,11 +104,8 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
|
||||||
for (int i = 0; i < mediaSources.length; i++) {
|
for (int i = 0; i < mediaSources.length; i++) {
|
||||||
prepareChildSource(i, mediaSources[i]);
|
prepareChildSource(i, mediaSources[i]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
|
|
@ -304,10 +303,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
transferListener = mediaTransferListener;
|
transferListener = mediaTransferListener;
|
||||||
refreshSourceInfo(timeline, /* manifest= */ null);
|
refreshSourceInfo(timeline, /* manifest= */ null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source.ads;
|
package com.google.android.exoplayer2.source.ads;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException;
|
import com.google.android.exoplayer2.source.ads.AdsMediaSource.AdLoadException;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -25,27 +27,25 @@ import java.io.IOException;
|
||||||
/**
|
/**
|
||||||
* Interface for loaders of ads, which can be used with {@link AdsMediaSource}.
|
* Interface for loaders of ads, which can be used with {@link AdsMediaSource}.
|
||||||
*
|
*
|
||||||
* <p>Ad loaders notify the {@link AdsMediaSource} about events via {@link EventListener}. In
|
* <p>Ads loaders notify the {@link AdsMediaSource} about events via {@link EventListener}. In
|
||||||
* particular, implementations must call {@link EventListener#onAdPlaybackState(AdPlaybackState)}
|
* particular, implementations must call {@link EventListener#onAdPlaybackState(AdPlaybackState)}
|
||||||
* with a new copy of the current {@link AdPlaybackState} whenever further information about ads
|
* with a new copy of the current {@link AdPlaybackState} whenever further information about ads
|
||||||
* becomes known (for example, when an ad media URI is available, or an ad has played to the end).
|
* becomes known (for example, when an ad media URI is available, or an ad has played to the end).
|
||||||
*
|
*
|
||||||
* <p>{@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} will be called when the ads media
|
* <p>{@link #start(EventListener, AdViewProvider)} will be called when the ads media source first
|
||||||
* source first initializes, at which point the loader can request ads. If the player enters the
|
* initializes, at which point the loader can request ads. If the player enters the background,
|
||||||
* background, {@link #detachPlayer()} will be called. Loaders should maintain any ad playback state
|
* {@link #stop()} will be called. Loaders should maintain any ad playback state in preparation for
|
||||||
* in preparation for a later call to {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. If
|
* a later call to {@link #start(EventListener, AdViewProvider)}. If an ad is playing when the
|
||||||
* an ad is playing when the player is detached, update the ad playback state with the current
|
* player is detached, update the ad playback state with the current playback position using {@link
|
||||||
* playback position using {@link AdPlaybackState#withAdResumePositionUs(long)}.
|
* AdPlaybackState#withAdResumePositionUs(long)}.
|
||||||
*
|
*
|
||||||
* <p>If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the
|
* <p>If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the
|
||||||
* implementation of {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} should invoke the
|
* implementation of {@link #start(EventListener, AdViewProvider)} should invoke the same listener
|
||||||
* same listener to provide the existing playback state to the new player.
|
* to provide the existing playback state to the new player.
|
||||||
*/
|
*/
|
||||||
public interface AdsLoader {
|
public interface AdsLoader {
|
||||||
|
|
||||||
/**
|
/** Listener for ads loader events. All methods are called on the main thread. */
|
||||||
* Listener for ad loader events. All methods are called on the main thread.
|
|
||||||
*/
|
|
||||||
interface EventListener {
|
interface EventListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -53,7 +53,7 @@ public interface AdsLoader {
|
||||||
*
|
*
|
||||||
* @param adPlaybackState The new ad playback state.
|
* @param adPlaybackState The new ad playback state.
|
||||||
*/
|
*/
|
||||||
void onAdPlaybackState(AdPlaybackState adPlaybackState);
|
default void onAdPlaybackState(AdPlaybackState adPlaybackState) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when there was an error loading ads.
|
* Called when there was an error loading ads.
|
||||||
|
|
@ -61,23 +61,62 @@ public interface AdsLoader {
|
||||||
* @param error The error.
|
* @param error The error.
|
||||||
* @param dataSpec The data spec associated with the load error.
|
* @param dataSpec The data spec associated with the load error.
|
||||||
*/
|
*/
|
||||||
void onAdLoadError(AdLoadException error, DataSpec dataSpec);
|
default void onAdLoadError(AdLoadException error, DataSpec dataSpec) {}
|
||||||
|
|
||||||
/**
|
/** Called when the user clicks through an ad (for example, following a 'learn more' link). */
|
||||||
* Called when the user clicks through an ad (for example, following a 'learn more' link).
|
default void onAdClicked() {}
|
||||||
*/
|
|
||||||
void onAdClicked();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user taps a non-clickthrough part of an ad.
|
|
||||||
*/
|
|
||||||
void onAdTapped();
|
|
||||||
|
|
||||||
|
/** Called when the user taps a non-clickthrough part of an ad. */
|
||||||
|
default void onAdTapped() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Provides views for the ad UI. */
|
||||||
|
interface AdViewProvider {
|
||||||
|
|
||||||
|
/** Returns the {@link ViewGroup} on top of the player that will show any ad UI. */
|
||||||
|
ViewGroup getAdViewGroup();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of views that are shown on top of the ad view group, but that are essential
|
||||||
|
* for controlling playback and should be excluded from ad viewability measurements by the
|
||||||
|
* {@link AdsLoader} (if it supports this).
|
||||||
|
*
|
||||||
|
* <p>Each view must be either a fully transparent overlay (for capturing touch events), or a
|
||||||
|
* small piece of transient UI that is essential to the user experience of playback (such as a
|
||||||
|
* button to pause/resume playback or a transient full-screen or cast button). For more
|
||||||
|
* information see the documentation for your ads loader.
|
||||||
|
*/
|
||||||
|
View[] getAdOverlayViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods called by the application.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the supported content types for ad media. Must be called before the first call to
|
* Sets the player that will play the loaded ads.
|
||||||
* {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. Subsequent calls may be ignored.
|
*
|
||||||
|
* <p>This method must be called before the player is prepared with media using this ads loader.
|
||||||
|
*
|
||||||
|
* <p>This method must also be called on the main thread and only players which are accessed on
|
||||||
|
* the main thread are supported ({@code player.getApplicationLooper() ==
|
||||||
|
* Looper.getMainLooper()}).
|
||||||
|
*
|
||||||
|
* @param player The player instance that will play the loaded ads. May be null to delete the
|
||||||
|
* reference to a previously set player.
|
||||||
|
*/
|
||||||
|
void setPlayer(@Nullable Player player);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the loader. Must be called by the application on the main thread when the instance is
|
||||||
|
* no longer needed.
|
||||||
|
*/
|
||||||
|
void release();
|
||||||
|
|
||||||
|
// Methods called by AdsMediaSource.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the supported content types for ad media. Must be called before the first call to {@link
|
||||||
|
* #start(EventListener, AdViewProvider)}. Subsequent calls may be ignored. Called on the main
|
||||||
|
* thread by {@link AdsMediaSource}.
|
||||||
*
|
*
|
||||||
* @param contentTypes The supported content types for ad media. Each element must be one of
|
* @param contentTypes The supported content types for ad media. Each element must be one of
|
||||||
* {@link C#TYPE_DASH}, {@link C#TYPE_HLS}, {@link C#TYPE_SS} and {@link C#TYPE_OTHER}.
|
* {@link C#TYPE_DASH}, {@link C#TYPE_HLS}, {@link C#TYPE_SS} and {@link C#TYPE_OTHER}.
|
||||||
|
|
@ -85,32 +124,23 @@ public interface AdsLoader {
|
||||||
void setSupportedContentTypes(@C.ContentType int... contentTypes);
|
void setSupportedContentTypes(@C.ContentType int... contentTypes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaches a player that will play ads loaded using this instance. Called on the main thread by
|
* Starts using the ads loader for playback. Called on the main thread by {@link AdsMediaSource}.
|
||||||
* {@link AdsMediaSource}.
|
|
||||||
*
|
*
|
||||||
* @param player The player instance that will play the loaded ads. Only players which are
|
|
||||||
* accessed on the main thread are supported ({@code player.getApplicationLooper() ==
|
|
||||||
* Looper.getMainLooper()}).
|
|
||||||
* @param eventListener Listener for ads loader events.
|
* @param eventListener Listener for ads loader events.
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
* @param adViewProvider Provider of views for the ad UI.
|
||||||
*/
|
*/
|
||||||
void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup);
|
void start(EventListener eventListener, AdViewProvider adViewProvider);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detaches the attached player and event listener. Called on the main thread by
|
* Stops using the ads loader for playback and deregisters the event listener. Called on the main
|
||||||
* {@link AdsMediaSource}.
|
* thread by {@link AdsMediaSource}.
|
||||||
*/
|
*/
|
||||||
void detachPlayer();
|
void stop();
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases the loader. Called by the application on the main thread when the instance is no
|
|
||||||
* longer needed.
|
|
||||||
*/
|
|
||||||
void release();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the ads loader that the player was not able to prepare media for a given ad.
|
* Notifies the ads loader that the player was not able to prepare media for a given ad.
|
||||||
* Implementations should update the ad playback state as the specified ad has failed to load.
|
* Implementations should update the ad playback state as the specified ad has failed to load.
|
||||||
|
* Called on the main thread by {@link AdsMediaSource}.
|
||||||
*
|
*
|
||||||
* @param adGroupIndex The index of the ad group.
|
* @param adGroupIndex The index of the ad group.
|
||||||
* @param adIndexInAdGroup The index of the ad in the ad group.
|
* @param adIndexInAdGroup The index of the ad in the ad group.
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,7 @@ import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.CompositeMediaSource;
|
import com.google.android.exoplayer2.source.CompositeMediaSource;
|
||||||
import com.google.android.exoplayer2.source.DeferredMediaPeriod;
|
import com.google.android.exoplayer2.source.DeferredMediaPeriod;
|
||||||
|
|
@ -140,46 +138,6 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for ads media source events.
|
|
||||||
*
|
|
||||||
* @deprecated To listen for ad load error events, add a listener via {@link
|
|
||||||
* #addEventListener(Handler, MediaSourceEventListener)} and check for {@link
|
|
||||||
* AdLoadException}s in {@link MediaSourceEventListener#onLoadError(int, MediaPeriodId,
|
|
||||||
* LoadEventInfo, MediaLoadData, IOException, boolean)}. Individual ads loader implementations
|
|
||||||
* should expose ad interaction events, if applicable.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public interface EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called if there was an error loading one or more ads. The loader will skip the problematic
|
|
||||||
* ad(s).
|
|
||||||
*
|
|
||||||
* @param error The error.
|
|
||||||
*/
|
|
||||||
void onAdLoadError(IOException error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an unexpected internal error is encountered while loading ads. The loader will
|
|
||||||
* skip all remaining ads, as the error is not recoverable.
|
|
||||||
*
|
|
||||||
* @param error The error.
|
|
||||||
*/
|
|
||||||
void onInternalAdLoadError(RuntimeException error);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user clicks through an ad (for example, following a 'learn more' link).
|
|
||||||
*/
|
|
||||||
void onAdClicked();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the user taps a non-clickthrough part of an ad.
|
|
||||||
*/
|
|
||||||
void onAdTapped();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to identify the content "child" source for CompositeMediaSource.
|
// Used to identify the content "child" source for CompositeMediaSource.
|
||||||
private static final MediaPeriodId DUMMY_CONTENT_MEDIA_PERIOD_ID =
|
private static final MediaPeriodId DUMMY_CONTENT_MEDIA_PERIOD_ID =
|
||||||
new MediaPeriodId(/* periodUid= */ new Object());
|
new MediaPeriodId(/* periodUid= */ new Object());
|
||||||
|
|
@ -187,9 +145,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
private final MediaSource contentMediaSource;
|
private final MediaSource contentMediaSource;
|
||||||
private final MediaSourceFactory adMediaSourceFactory;
|
private final MediaSourceFactory adMediaSourceFactory;
|
||||||
private final AdsLoader adsLoader;
|
private final AdsLoader adsLoader;
|
||||||
private final ViewGroup adUiViewGroup;
|
private final AdsLoader.AdViewProvider adViewProvider;
|
||||||
@Nullable private final Handler eventHandler;
|
|
||||||
@Nullable private final EventListener eventListener;
|
|
||||||
private final Handler mainHandler;
|
private final Handler mainHandler;
|
||||||
private final Map<MediaSource, List<DeferredMediaPeriod>> deferredMediaPeriodByAdMediaSource;
|
private final Map<MediaSource, List<DeferredMediaPeriod>> deferredMediaPeriodByAdMediaSource;
|
||||||
private final Timeline.Period period;
|
private final Timeline.Period period;
|
||||||
|
|
@ -209,20 +165,18 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
||||||
* @param dataSourceFactory Factory for data sources used to load ad media.
|
* @param dataSourceFactory Factory for data sources used to load ad media.
|
||||||
* @param adsLoader The loader for ads.
|
* @param adsLoader The loader for ads.
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
* @param adViewProvider Provider of views for the ad UI.
|
||||||
*/
|
*/
|
||||||
public AdsMediaSource(
|
public AdsMediaSource(
|
||||||
MediaSource contentMediaSource,
|
MediaSource contentMediaSource,
|
||||||
DataSource.Factory dataSourceFactory,
|
DataSource.Factory dataSourceFactory,
|
||||||
AdsLoader adsLoader,
|
AdsLoader adsLoader,
|
||||||
ViewGroup adUiViewGroup) {
|
AdsLoader.AdViewProvider adViewProvider) {
|
||||||
this(
|
this(
|
||||||
contentMediaSource,
|
contentMediaSource,
|
||||||
new ExtractorMediaSource.Factory(dataSourceFactory),
|
new ExtractorMediaSource.Factory(dataSourceFactory),
|
||||||
adsLoader,
|
adsLoader,
|
||||||
adUiViewGroup,
|
adViewProvider);
|
||||||
/* eventHandler= */ null,
|
|
||||||
/* eventListener= */ null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -232,85 +186,17 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
||||||
* @param adMediaSourceFactory Factory for media sources used to load ad media.
|
* @param adMediaSourceFactory Factory for media sources used to load ad media.
|
||||||
* @param adsLoader The loader for ads.
|
* @param adsLoader The loader for ads.
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
* @param adViewProvider Provider of views for the ad UI.
|
||||||
*/
|
*/
|
||||||
public AdsMediaSource(
|
public AdsMediaSource(
|
||||||
MediaSource contentMediaSource,
|
MediaSource contentMediaSource,
|
||||||
MediaSourceFactory adMediaSourceFactory,
|
MediaSourceFactory adMediaSourceFactory,
|
||||||
AdsLoader adsLoader,
|
AdsLoader adsLoader,
|
||||||
ViewGroup adUiViewGroup) {
|
AdsLoader.AdViewProvider adViewProvider) {
|
||||||
this(
|
|
||||||
contentMediaSource,
|
|
||||||
adMediaSourceFactory,
|
|
||||||
adsLoader,
|
|
||||||
adUiViewGroup,
|
|
||||||
/* eventHandler= */ null,
|
|
||||||
/* eventListener= */ null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new source that inserts ads linearly with the content specified by {@code
|
|
||||||
* contentMediaSource}. Ad media is loaded using {@link ExtractorMediaSource}.
|
|
||||||
*
|
|
||||||
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
|
||||||
* @param dataSourceFactory Factory for data sources used to load ad media.
|
|
||||||
* @param adsLoader The loader for ads.
|
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
|
||||||
* @param eventHandler A handler for events. May be null if delivery of events is not required.
|
|
||||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
|
||||||
* @deprecated To listen for ad load error events, add a listener via {@link
|
|
||||||
* #addEventListener(Handler, MediaSourceEventListener)} and check for {@link
|
|
||||||
* AdLoadException}s in {@link MediaSourceEventListener#onLoadError(int, MediaPeriodId,
|
|
||||||
* LoadEventInfo, MediaLoadData, IOException, boolean)}. Individual ads loader implementations
|
|
||||||
* should expose ad interaction events, if applicable.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public AdsMediaSource(
|
|
||||||
MediaSource contentMediaSource,
|
|
||||||
DataSource.Factory dataSourceFactory,
|
|
||||||
AdsLoader adsLoader,
|
|
||||||
ViewGroup adUiViewGroup,
|
|
||||||
@Nullable Handler eventHandler,
|
|
||||||
@Nullable EventListener eventListener) {
|
|
||||||
this(
|
|
||||||
contentMediaSource,
|
|
||||||
new ExtractorMediaSource.Factory(dataSourceFactory),
|
|
||||||
adsLoader,
|
|
||||||
adUiViewGroup,
|
|
||||||
eventHandler,
|
|
||||||
eventListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new source that inserts ads linearly with the content specified by {@code
|
|
||||||
* contentMediaSource}.
|
|
||||||
*
|
|
||||||
* @param contentMediaSource The {@link MediaSource} providing the content to play.
|
|
||||||
* @param adMediaSourceFactory Factory for media sources used to load ad media.
|
|
||||||
* @param adsLoader The loader for ads.
|
|
||||||
* @param adUiViewGroup A {@link ViewGroup} on top of the player that will show any ad UI.
|
|
||||||
* @param eventHandler A handler for events. May be null if delivery of events is not required.
|
|
||||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
|
||||||
* @deprecated To listen for ad load error events, add a listener via {@link
|
|
||||||
* #addEventListener(Handler, MediaSourceEventListener)} and check for {@link
|
|
||||||
* AdLoadException}s in {@link MediaSourceEventListener#onLoadError(int, MediaPeriodId,
|
|
||||||
* LoadEventInfo, MediaLoadData, IOException, boolean)}. Individual ads loader implementations
|
|
||||||
* should expose ad interaction events, if applicable.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public AdsMediaSource(
|
|
||||||
MediaSource contentMediaSource,
|
|
||||||
MediaSourceFactory adMediaSourceFactory,
|
|
||||||
AdsLoader adsLoader,
|
|
||||||
ViewGroup adUiViewGroup,
|
|
||||||
@Nullable Handler eventHandler,
|
|
||||||
@Nullable EventListener eventListener) {
|
|
||||||
this.contentMediaSource = contentMediaSource;
|
this.contentMediaSource = contentMediaSource;
|
||||||
this.adMediaSourceFactory = adMediaSourceFactory;
|
this.adMediaSourceFactory = adMediaSourceFactory;
|
||||||
this.adsLoader = adsLoader;
|
this.adsLoader = adsLoader;
|
||||||
this.adUiViewGroup = adUiViewGroup;
|
this.adViewProvider = adViewProvider;
|
||||||
this.eventHandler = eventHandler;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
mainHandler = new Handler(Looper.getMainLooper());
|
mainHandler = new Handler(Looper.getMainLooper());
|
||||||
deferredMediaPeriodByAdMediaSource = new HashMap<>();
|
deferredMediaPeriodByAdMediaSource = new HashMap<>();
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
|
|
@ -326,18 +212,12 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
final ExoPlayer player,
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
boolean isTopLevelSource,
|
ComponentListener componentListener = new ComponentListener();
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
|
||||||
Assertions.checkArgument(
|
|
||||||
isTopLevelSource,
|
|
||||||
"AdsMediaSource must be the top-level source used to prepare the player.");
|
|
||||||
final ComponentListener componentListener = new ComponentListener();
|
|
||||||
this.componentListener = componentListener;
|
this.componentListener = componentListener;
|
||||||
prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource);
|
prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource);
|
||||||
mainHandler.post(() -> adsLoader.attachPlayer(player, componentListener, adUiViewGroup));
|
mainHandler.post(() -> adsLoader.start(componentListener, adViewProvider));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -406,7 +286,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
adPlaybackState = null;
|
adPlaybackState = null;
|
||||||
adGroupMediaSources = new MediaSource[0][];
|
adGroupMediaSources = new MediaSource[0][];
|
||||||
adGroupTimelines = new Timeline[0][];
|
adGroupTimelines = new Timeline[0][];
|
||||||
mainHandler.post(adsLoader::detachPlayer);
|
mainHandler.post(adsLoader::stop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -446,6 +326,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onContentSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
private void onContentSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||||
|
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
||||||
contentTimeline = timeline;
|
contentTimeline = timeline;
|
||||||
contentManifest = manifest;
|
contentManifest = manifest;
|
||||||
maybeUpdateSourceInfo();
|
maybeUpdateSourceInfo();
|
||||||
|
|
@ -528,36 +409,6 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAdClicked() {
|
|
||||||
if (released) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (eventHandler != null && eventListener != null) {
|
|
||||||
eventHandler.post(
|
|
||||||
() -> {
|
|
||||||
if (!released) {
|
|
||||||
eventListener.onAdClicked();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAdTapped() {
|
|
||||||
if (released) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (eventHandler != null && eventListener != null) {
|
|
||||||
eventHandler.post(
|
|
||||||
() -> {
|
|
||||||
if (!released) {
|
|
||||||
eventListener.onAdTapped();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdLoadError(final AdLoadException error, DataSpec dataSpec) {
|
public void onAdLoadError(final AdLoadException error, DataSpec dataSpec) {
|
||||||
if (released) {
|
if (released) {
|
||||||
|
|
@ -574,18 +425,6 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
||||||
/* bytesLoaded= */ 0,
|
/* bytesLoaded= */ 0,
|
||||||
error,
|
error,
|
||||||
/* wasCanceled= */ true);
|
/* wasCanceled= */ true);
|
||||||
if (eventHandler != null && eventListener != null) {
|
|
||||||
eventHandler.post(
|
|
||||||
() -> {
|
|
||||||
if (!released) {
|
|
||||||
if (error.type == AdLoadException.TYPE_UNEXPECTED) {
|
|
||||||
eventListener.onInternalAdLoadError(error.getRuntimeExceptionForUnexpected());
|
|
||||||
} else {
|
|
||||||
eventListener.onAdLoadError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1087,10 +1087,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
throws DecoderQueryException {
|
throws DecoderQueryException {
|
||||||
int maxWidth = format.width;
|
int maxWidth = format.width;
|
||||||
int maxHeight = format.height;
|
int maxHeight = format.height;
|
||||||
if (codecNeedsMaxVideoSizeResetWorkaround(codecInfo.name)) {
|
|
||||||
maxWidth = Math.max(maxWidth, 1920);
|
|
||||||
maxHeight = Math.max(maxHeight, 1089);
|
|
||||||
}
|
|
||||||
int maxInputSize = getMaxInputSize(codecInfo, format);
|
int maxInputSize = getMaxInputSize(codecInfo, format);
|
||||||
if (streamFormats.length == 1) {
|
if (streamFormats.length == 1) {
|
||||||
// The single entry in streamFormats must correspond to the format for which the codec is
|
// The single entry in streamFormats must correspond to the format for which the codec is
|
||||||
|
|
@ -1278,18 +1274,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
return "NVIDIA".equals(Util.MANUFACTURER);
|
return "NVIDIA".equals(Util.MANUFACTURER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the codec is known to have problems with the configuration for interlaced
|
|
||||||
* content and needs minimum values for the maximum video size to force reset the configuration.
|
|
||||||
*
|
|
||||||
* <p>See https://github.com/google/ExoPlayer/issues/5003.
|
|
||||||
*
|
|
||||||
* @param name The name of the codec.
|
|
||||||
*/
|
|
||||||
private static boolean codecNeedsMaxVideoSizeResetWorkaround(String name) {
|
|
||||||
return "OMX.amlogic.avc.decoder.awesome".equals(name) && Util.SDK_INT <= 25;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO:
|
* TODO:
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -267,10 +267,8 @@ public final class ExoPlayerTest {
|
||||||
new FakeMediaSource(timeline, new Object(), Builder.VIDEO_FORMAT) {
|
new FakeMediaSource(timeline, new Object(), Builder.VIDEO_FORMAT) {
|
||||||
@Override
|
@Override
|
||||||
public synchronized void prepareSourceInternal(
|
public synchronized void prepareSourceInternal(
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
@Nullable TransferListener mediaTransferListener) {
|
||||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
// We've queued a source info refresh on the playback thread's event queue. Allow the
|
// We've queued a source info refresh on the playback thread's event queue. Allow the
|
||||||
// test thread to prepare the player with the third source, and block this thread (the
|
// test thread to prepare the player with the third source, and block this thread (the
|
||||||
// playback thread) until the test thread's call to prepare() has returned.
|
// playback thread) until the test thread's call to prepare() has returned.
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,6 @@ public final class EventMessageDecoderTest {
|
||||||
assertThat(eventMessage.durationMs).isEqualTo(3000);
|
assertThat(eventMessage.durationMs).isEqualTo(3000);
|
||||||
assertThat(eventMessage.id).isEqualTo(1000403);
|
assertThat(eventMessage.id).isEqualTo(1000403);
|
||||||
assertThat(eventMessage.messageData).isEqualTo(new byte[]{0, 1, 2, 3, 4});
|
assertThat(eventMessage.messageData).isEqualTo(new byte[]{0, 1, 2, 3, 4});
|
||||||
assertThat(eventMessage.presentationTimeUs).isEqualTo(1000000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,25 +33,27 @@ public final class EventMessageEncoderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeEventStream() throws IOException {
|
public void testEncodeEventStream() throws IOException {
|
||||||
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
|
EventMessage eventMessage =
|
||||||
new byte[] {0, 1, 2, 3, 4}, 1000000);
|
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||||
byte[] expectedEmsgBody = new byte[] {
|
byte[] expectedEmsgBody =
|
||||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
new byte[] {
|
||||||
49, 50, 51, 0, // value = "123"
|
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||||
0, 0, -69, -128, // timescale = 48000
|
49, 50, 51, 0, // value = "123"
|
||||||
0, 0, -69, -128, // presentation_time_delta = 48000
|
0, 0, 3, -24, // timescale = 1000
|
||||||
0, 2, 50, -128, // event_duration = 144000
|
0, 0, 0, 0, // presentation_time_delta = 0
|
||||||
0, 15, 67, -45, // id = 1000403
|
0, 0, 11, -72, // event_duration = 3000
|
||||||
0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4}
|
0, 15, 67, -45, // id = 1000403
|
||||||
byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage, 48000);
|
0, 1, 2, 3, 4
|
||||||
|
}; // message_data = {0, 1, 2, 3, 4}
|
||||||
|
byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage);
|
||||||
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
|
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeDecodeEventStream() throws IOException {
|
public void testEncodeDecodeEventStream() throws IOException {
|
||||||
EventMessage expectedEmsg = new EventMessage("urn:test", "123", 3000, 1000403,
|
EventMessage expectedEmsg =
|
||||||
new byte[] {0, 1, 2, 3, 4}, 1000000);
|
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||||
byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg, 48000);
|
byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg);
|
||||||
MetadataInputBuffer buffer = new MetadataInputBuffer();
|
MetadataInputBuffer buffer = new MetadataInputBuffer();
|
||||||
buffer.data = ByteBuffer.allocate(encodedByteArray.length).put(encodedByteArray);
|
buffer.data = ByteBuffer.allocate(encodedByteArray.length).put(encodedByteArray);
|
||||||
|
|
||||||
|
|
@ -63,30 +65,34 @@ public final class EventMessageEncoderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeEventStreamMultipleTimesWorkingCorrectly() throws IOException {
|
public void testEncodeEventStreamMultipleTimesWorkingCorrectly() throws IOException {
|
||||||
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
|
EventMessage eventMessage =
|
||||||
new byte[] {0, 1, 2, 3, 4}, 1000000);
|
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||||
byte[] expectedEmsgBody = new byte[] {
|
byte[] expectedEmsgBody =
|
||||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
new byte[] {
|
||||||
49, 50, 51, 0, // value = "123"
|
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||||
0, 0, -69, -128, // timescale = 48000
|
49, 50, 51, 0, // value = "123"
|
||||||
0, 0, -69, -128, // presentation_time_delta = 48000
|
0, 0, 3, -24, // timescale = 1000
|
||||||
0, 2, 50, -128, // event_duration = 144000
|
0, 0, 0, 0, // presentation_time_delta = 0
|
||||||
0, 15, 67, -45, // id = 1000403
|
0, 0, 11, -72, // event_duration = 3000
|
||||||
0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4}
|
0, 15, 67, -45, // id = 1000403
|
||||||
EventMessage eventMessage1 = new EventMessage("urn:test", "123", 3000, 1000402,
|
0, 1, 2, 3, 4
|
||||||
new byte[] {4, 3, 2, 1, 0}, 1000000);
|
}; // message_data = {0, 1, 2, 3, 4}
|
||||||
byte[] expectedEmsgBody1 = new byte[] {
|
EventMessage eventMessage1 =
|
||||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
new EventMessage("urn:test", "123", 3000, 1000402, new byte[] {4, 3, 2, 1, 0});
|
||||||
49, 50, 51, 0, // value = "123"
|
byte[] expectedEmsgBody1 =
|
||||||
0, 0, -69, -128, // timescale = 48000
|
new byte[] {
|
||||||
0, 0, -69, -128, // presentation_time_delta = 48000
|
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||||
0, 2, 50, -128, // event_duration = 144000
|
49, 50, 51, 0, // value = "123"
|
||||||
0, 15, 67, -46, // id = 1000402
|
0, 0, 3, -24, // timescale = 1000
|
||||||
4, 3, 2, 1, 0}; // message_data = {4, 3, 2, 1, 0}
|
0, 0, 0, 0, // presentation_time_delta = 0
|
||||||
|
0, 0, 11, -72, // event_duration = 3000
|
||||||
|
0, 15, 67, -46, // id = 1000402
|
||||||
|
4, 3, 2, 1, 0
|
||||||
|
}; // message_data = {4, 3, 2, 1, 0}
|
||||||
EventMessageEncoder eventMessageEncoder = new EventMessageEncoder();
|
EventMessageEncoder eventMessageEncoder = new EventMessageEncoder();
|
||||||
byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage, 48000);
|
byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage);
|
||||||
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
|
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
|
||||||
byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1, 48000);
|
byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1);
|
||||||
assertThat(encodedByteArray1).isEqualTo(expectedEmsgBody1);
|
assertThat(encodedByteArray1).isEqualTo(expectedEmsgBody1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ public final class EventMessageTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEventMessageParcelable() {
|
public void testEventMessageParcelable() {
|
||||||
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
|
EventMessage eventMessage =
|
||||||
new byte[] {0, 1, 2, 3, 4}, 1000);
|
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||||
// Write to parcel.
|
// Write to parcel.
|
||||||
Parcel parcel = Parcel.obtain();
|
Parcel parcel = Parcel.obtain();
|
||||||
eventMessage.writeToParcel(parcel, 0);
|
eventMessage.writeToParcel(parcel, 0);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
|
@ -614,10 +613,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
this.mediaTransferListener = mediaTransferListener;
|
this.mediaTransferListener = mediaTransferListener;
|
||||||
if (sideloadedManifest) {
|
if (sideloadedManifest) {
|
||||||
processManifest(false);
|
processManifest(false);
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,7 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int sampleIndex = currentIndex++;
|
int sampleIndex = currentIndex++;
|
||||||
byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex],
|
byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex]);
|
||||||
eventStream.timescale);
|
|
||||||
if (serializedEvent != null) {
|
if (serializedEvent != null) {
|
||||||
buffer.ensureSpaceForWrite(serializedEvent.length);
|
buffer.ensureSpaceForWrite(serializedEvent.length);
|
||||||
buffer.setFlags(C.BUFFER_FLAG_KEY_FRAME);
|
buffer.setFlags(C.BUFFER_FLAG_KEY_FRAME);
|
||||||
|
|
|
||||||
|
|
@ -834,13 +834,13 @@ public class DashManifestParser extends DefaultHandler
|
||||||
String schemeIdUri = parseString(xpp, "schemeIdUri", "");
|
String schemeIdUri = parseString(xpp, "schemeIdUri", "");
|
||||||
String value = parseString(xpp, "value", "");
|
String value = parseString(xpp, "value", "");
|
||||||
long timescale = parseLong(xpp, "timescale", 1);
|
long timescale = parseLong(xpp, "timescale", 1);
|
||||||
List<EventMessage> eventMessages = new ArrayList<>();
|
List<Pair<Long, EventMessage>> eventMessages = new ArrayList<>();
|
||||||
ByteArrayOutputStream scratchOutputStream = new ByteArrayOutputStream(512);
|
ByteArrayOutputStream scratchOutputStream = new ByteArrayOutputStream(512);
|
||||||
do {
|
do {
|
||||||
xpp.next();
|
xpp.next();
|
||||||
if (XmlPullParserUtil.isStartTag(xpp, "Event")) {
|
if (XmlPullParserUtil.isStartTag(xpp, "Event")) {
|
||||||
EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale,
|
Pair<Long, EventMessage> event =
|
||||||
scratchOutputStream);
|
parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream);
|
||||||
eventMessages.add(event);
|
eventMessages.add(event);
|
||||||
} else {
|
} else {
|
||||||
maybeSkipTag(xpp);
|
maybeSkipTag(xpp);
|
||||||
|
|
@ -850,9 +850,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
long[] presentationTimesUs = new long[eventMessages.size()];
|
long[] presentationTimesUs = new long[eventMessages.size()];
|
||||||
EventMessage[] events = new EventMessage[eventMessages.size()];
|
EventMessage[] events = new EventMessage[eventMessages.size()];
|
||||||
for (int i = 0; i < eventMessages.size(); i++) {
|
for (int i = 0; i < eventMessages.size(); i++) {
|
||||||
EventMessage event = eventMessages.get(i);
|
Pair<Long, EventMessage> event = eventMessages.get(i);
|
||||||
presentationTimesUs[i] = event.presentationTimeUs;
|
presentationTimesUs[i] = event.first;
|
||||||
events[i] = event;
|
events[i] = event.second;
|
||||||
}
|
}
|
||||||
return buildEventStream(schemeIdUri, value, timescale, presentationTimesUs, events);
|
return buildEventStream(schemeIdUri, value, timescale, presentationTimesUs, events);
|
||||||
}
|
}
|
||||||
|
|
@ -871,11 +871,12 @@ public class DashManifestParser extends DefaultHandler
|
||||||
* @param timescale The timescale of the parent EventStream.
|
* @param timescale The timescale of the parent EventStream.
|
||||||
* @param scratchOutputStream A {@link ByteArrayOutputStream} that is used when parsing event
|
* @param scratchOutputStream A {@link ByteArrayOutputStream} that is used when parsing event
|
||||||
* objects.
|
* objects.
|
||||||
* @return The {@link EventMessage} parsed from this EventStream node.
|
* @return A pair containing the node's presentation timestamp in microseconds and the parsed
|
||||||
|
* {@link EventMessage}.
|
||||||
* @throws XmlPullParserException If there is any error parsing this node.
|
* @throws XmlPullParserException If there is any error parsing this node.
|
||||||
* @throws IOException If there is any error reading from the underlying input stream.
|
* @throws IOException If there is any error reading from the underlying input stream.
|
||||||
*/
|
*/
|
||||||
protected EventMessage parseEvent(
|
protected Pair<Long, EventMessage> parseEvent(
|
||||||
XmlPullParser xpp,
|
XmlPullParser xpp,
|
||||||
String schemeIdUri,
|
String schemeIdUri,
|
||||||
String value,
|
String value,
|
||||||
|
|
@ -890,13 +891,14 @@ public class DashManifestParser extends DefaultHandler
|
||||||
timescale);
|
timescale);
|
||||||
String messageData = parseString(xpp, "messageData", null);
|
String messageData = parseString(xpp, "messageData", null);
|
||||||
byte[] eventObject = parseEventObject(xpp, scratchOutputStream);
|
byte[] eventObject = parseEventObject(xpp, scratchOutputStream);
|
||||||
return buildEvent(
|
return Pair.create(
|
||||||
schemeIdUri,
|
presentationTimesUs,
|
||||||
value,
|
buildEvent(
|
||||||
id,
|
schemeIdUri,
|
||||||
durationMs,
|
value,
|
||||||
messageData == null ? eventObject : Util.getUtf8Bytes(messageData),
|
id,
|
||||||
presentationTimesUs);
|
durationMs,
|
||||||
|
messageData == null ? eventObject : Util.getUtf8Bytes(messageData)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -963,9 +965,9 @@ public class DashManifestParser extends DefaultHandler
|
||||||
return scratchOutputStream.toByteArray();
|
return scratchOutputStream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EventMessage buildEvent(String schemeIdUri, String value, long id,
|
protected EventMessage buildEvent(
|
||||||
long durationMs, byte[] messageData, long presentationTimeUs) {
|
String schemeIdUri, String value, long id, long durationMs, byte[] messageData) {
|
||||||
return new EventMessage(schemeIdUri, value, durationMs, id, messageData, presentationTimeUs);
|
return new EventMessage(schemeIdUri, value, durationMs, id, messageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<SegmentTimelineElement> parseSegmentTimeline(XmlPullParser xpp)
|
protected List<SegmentTimelineElement> parseSegmentTimeline(XmlPullParser xpp)
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ public final class EventSampleStreamTest {
|
||||||
@Test
|
@Test
|
||||||
public void testReadDataReturnDataAfterFormat() {
|
public void testReadDataReturnDataAfterFormat() {
|
||||||
long presentationTimeUs = 1000000;
|
long presentationTimeUs = 1000000;
|
||||||
EventMessage eventMessage = newEventMessageWithIdAndTime(1, presentationTimeUs);
|
EventMessage eventMessage = newEventMessageWithId(1);
|
||||||
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs}, new EventMessage[] {eventMessage});
|
new long[] {presentationTimeUs}, new EventMessage[] {eventMessage});
|
||||||
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
|
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
|
||||||
|
|
@ -133,8 +133,8 @@ public final class EventSampleStreamTest {
|
||||||
public void testSkipDataThenReadDataReturnDataFromSkippedPosition() {
|
public void testSkipDataThenReadDataReturnDataFromSkippedPosition() {
|
||||||
long presentationTimeUs1 = 1000000;
|
long presentationTimeUs1 = 1000000;
|
||||||
long presentationTimeUs2 = 2000000;
|
long presentationTimeUs2 = 2000000;
|
||||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||||
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||||
new EventMessage[] {eventMessage1, eventMessage2});
|
new EventMessage[] {eventMessage1, eventMessage2});
|
||||||
|
|
@ -159,8 +159,8 @@ public final class EventSampleStreamTest {
|
||||||
public void testSeekToUsThenReadDataReturnDataFromSeekPosition() {
|
public void testSeekToUsThenReadDataReturnDataFromSeekPosition() {
|
||||||
long presentationTimeUs1 = 1000000;
|
long presentationTimeUs1 = 1000000;
|
||||||
long presentationTimeUs2 = 2000000;
|
long presentationTimeUs2 = 2000000;
|
||||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||||
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||||
new EventMessage[] {eventMessage1, eventMessage2});
|
new EventMessage[] {eventMessage1, eventMessage2});
|
||||||
|
|
@ -186,9 +186,9 @@ public final class EventSampleStreamTest {
|
||||||
long presentationTimeUs1 = 1000000;
|
long presentationTimeUs1 = 1000000;
|
||||||
long presentationTimeUs2 = 2000000;
|
long presentationTimeUs2 = 2000000;
|
||||||
long presentationTimeUs3 = 3000000;
|
long presentationTimeUs3 = 3000000;
|
||||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||||
new EventMessage[] {eventMessage1, eventMessage2});
|
new EventMessage[] {eventMessage1, eventMessage2});
|
||||||
|
|
@ -220,9 +220,9 @@ public final class EventSampleStreamTest {
|
||||||
long presentationTimeUs1 = 1000000;
|
long presentationTimeUs1 = 1000000;
|
||||||
long presentationTimeUs2 = 2000000;
|
long presentationTimeUs2 = 2000000;
|
||||||
long presentationTimeUs3 = 3000000;
|
long presentationTimeUs3 = 3000000;
|
||||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||||
new EventMessage[] {eventMessage1, eventMessage2});
|
new EventMessage[] {eventMessage1, eventMessage2});
|
||||||
|
|
@ -253,9 +253,9 @@ public final class EventSampleStreamTest {
|
||||||
long presentationTimeUs1 = 1000000;
|
long presentationTimeUs1 = 1000000;
|
||||||
long presentationTimeUs2 = 2000000;
|
long presentationTimeUs2 = 2000000;
|
||||||
long presentationTimeUs3 = 3000000;
|
long presentationTimeUs3 = 3000000;
|
||||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs1},
|
new long[] {presentationTimeUs1},
|
||||||
new EventMessage[] {eventMessage1});
|
new EventMessage[] {eventMessage1});
|
||||||
|
|
@ -287,9 +287,9 @@ public final class EventSampleStreamTest {
|
||||||
long presentationTimeUs1 = 1000000;
|
long presentationTimeUs1 = 1000000;
|
||||||
long presentationTimeUs2 = 2000000;
|
long presentationTimeUs2 = 2000000;
|
||||||
long presentationTimeUs3 = 3000000;
|
long presentationTimeUs3 = 3000000;
|
||||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||||
new EventMessage[] {eventMessage1, eventMessage2});
|
new EventMessage[] {eventMessage1, eventMessage2});
|
||||||
|
|
@ -319,9 +319,9 @@ public final class EventSampleStreamTest {
|
||||||
long presentationTimeUs1 = 1000000;
|
long presentationTimeUs1 = 1000000;
|
||||||
long presentationTimeUs2 = 2000000;
|
long presentationTimeUs2 = 2000000;
|
||||||
long presentationTimeUs3 = 3000000;
|
long presentationTimeUs3 = 3000000;
|
||||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||||
new long[] {presentationTimeUs1},
|
new long[] {presentationTimeUs1},
|
||||||
new EventMessage[] {eventMessage1});
|
new EventMessage[] {eventMessage1});
|
||||||
|
|
@ -345,12 +345,12 @@ public final class EventSampleStreamTest {
|
||||||
return sampleStream.readData(formatHolder, inputBuffer, false);
|
return sampleStream.readData(formatHolder, inputBuffer, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EventMessage newEventMessageWithIdAndTime(int id, long presentationTimeUs) {
|
private EventMessage newEventMessageWithId(int id) {
|
||||||
return new EventMessage(SCHEME_ID, VALUE, DURATION_MS, id, MESSAGE_DATA, presentationTimeUs);
|
return new EventMessage(SCHEME_ID, VALUE, DURATION_MS, id, MESSAGE_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] getEncodedMessage(EventMessage eventMessage) {
|
private byte[] getEncodedMessage(EventMessage eventMessage) {
|
||||||
return eventMessageEncoder.encode(eventMessage, TIME_SCALE);
|
return eventMessageEncoder.encode(eventMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,52 +104,53 @@ public class DashManifestParserTest {
|
||||||
"call",
|
"call",
|
||||||
10000,
|
10000,
|
||||||
0,
|
0,
|
||||||
"+ 1 800 10101010".getBytes(Charset.forName(C.UTF8_NAME)),
|
"+ 1 800 10101010".getBytes(Charset.forName(C.UTF8_NAME)));
|
||||||
0);
|
|
||||||
assertThat(eventStream1.events[0]).isEqualTo(expectedEvent1);
|
assertThat(eventStream1.events[0]).isEqualTo(expectedEvent1);
|
||||||
|
assertThat(eventStream1.presentationTimesUs[0]).isEqualTo(0);
|
||||||
|
|
||||||
// assert CData-structured event stream
|
// assert CData-structured event stream
|
||||||
EventStream eventStream2 = period.eventStreams.get(1);
|
EventStream eventStream2 = period.eventStreams.get(1);
|
||||||
assertThat(eventStream2.events.length).isEqualTo(1);
|
assertThat(eventStream2.events.length).isEqualTo(1);
|
||||||
assertThat(eventStream2.events[0])
|
EventMessage expectedEvent2 =
|
||||||
.isEqualTo(
|
new EventMessage(
|
||||||
new EventMessage(
|
"urn:dvb:iptv:cpm:2014",
|
||||||
"urn:dvb:iptv:cpm:2014",
|
"",
|
||||||
"",
|
1500000,
|
||||||
1500000,
|
1,
|
||||||
1,
|
Util.getUtf8Bytes(
|
||||||
Util.getUtf8Bytes(
|
"<![CDATA[<BroadcastEvent>\n"
|
||||||
"<![CDATA[<BroadcastEvent>\n"
|
+ " <Program crid=\"crid://broadcaster.example.com/ABCDEF\"/>\n"
|
||||||
+ " <Program crid=\"crid://broadcaster.example.com/ABCDEF\"/>\n"
|
+ " <InstanceDescription>\n"
|
||||||
+ " <InstanceDescription>\n"
|
+ " <Title xml:lang=\"en\">The title</Title>\n"
|
||||||
+ " <Title xml:lang=\"en\">The title</Title>\n"
|
+ " <Synopsis xml:lang=\"en\" length=\"medium\">"
|
||||||
+ " <Synopsis xml:lang=\"en\" length=\"medium\">"
|
+ "The description</Synopsis>\n"
|
||||||
+ "The description</Synopsis>\n"
|
+ " <ParentalGuidance>\n"
|
||||||
+ " <ParentalGuidance>\n"
|
+ " <mpeg7:ParentalRating href=\"urn:dvb:iptv:rating:2014:15\"/>\n"
|
||||||
+ " <mpeg7:ParentalRating href=\"urn:dvb:iptv:rating:2014:15\"/>\n"
|
+ " <mpeg7:Region>GB</mpeg7:Region>\n"
|
||||||
+ " <mpeg7:Region>GB</mpeg7:Region>\n"
|
+ " </ParentalGuidance>\n"
|
||||||
+ " </ParentalGuidance>\n"
|
+ " </InstanceDescription>\n"
|
||||||
+ " </InstanceDescription>\n"
|
+ " </BroadcastEvent>]]>"));
|
||||||
+ " </BroadcastEvent>]]>"),
|
|
||||||
300000000));
|
assertThat(eventStream2.events[0]).isEqualTo(expectedEvent2);
|
||||||
|
assertThat(eventStream2.presentationTimesUs[0]).isEqualTo(300000000);
|
||||||
|
|
||||||
// assert xml-structured event stream
|
// assert xml-structured event stream
|
||||||
EventStream eventStream3 = period.eventStreams.get(2);
|
EventStream eventStream3 = period.eventStreams.get(2);
|
||||||
assertThat(eventStream3.events.length).isEqualTo(1);
|
assertThat(eventStream3.events.length).isEqualTo(1);
|
||||||
assertThat(eventStream3.events[0])
|
EventMessage expectedEvent3 =
|
||||||
.isEqualTo(
|
new EventMessage(
|
||||||
new EventMessage(
|
"urn:scte:scte35:2014:xml+bin",
|
||||||
"urn:scte:scte35:2014:xml+bin",
|
"",
|
||||||
"",
|
1000000,
|
||||||
1000000,
|
2,
|
||||||
2,
|
Util.getUtf8Bytes(
|
||||||
Util.getUtf8Bytes(
|
"<scte35:Signal>\n"
|
||||||
"<scte35:Signal>\n"
|
+ " <scte35:Binary>\n"
|
||||||
+ " <scte35:Binary>\n"
|
+ " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n"
|
||||||
+ " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n"
|
+ " </scte35:Binary>\n"
|
||||||
+ " </scte35:Binary>\n"
|
+ " </scte35:Signal>"));
|
||||||
+ " </scte35:Signal>"),
|
assertThat(eventStream3.events[0]).isEqualTo(expectedEvent3);
|
||||||
1000000000));
|
assertThat(eventStream3.presentationTimesUs[0]).isEqualTo(1000000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||||
|
|
@ -397,10 +396,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
this.mediaTransferListener = mediaTransferListener;
|
this.mediaTransferListener = mediaTransferListener;
|
||||||
EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
|
EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
|
||||||
playlistTracker.start(manifestUri, eventDispatcher, /* listener= */ this);
|
playlistTracker.start(manifestUri, eventDispatcher, /* listener= */ this);
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ import java.io.IOException;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
|
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
|
||||||
|
if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL) {
|
||||||
|
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||||
|
return C.RESULT_BUFFER_READ;
|
||||||
|
}
|
||||||
return hasValidSampleQueueIndex()
|
return hasValidSampleQueueIndex()
|
||||||
? sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, requireFormat)
|
? sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, requireFormat)
|
||||||
: C.RESULT_NOTHING_READ;
|
: C.RESULT_NOTHING_READ;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
|
@ -510,10 +509,7 @@ public final class SsMediaSource extends BaseMediaSource
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void prepareSourceInternal(
|
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
this.mediaTransferListener = mediaTransferListener;
|
this.mediaTransferListener = mediaTransferListener;
|
||||||
if (sideloadedManifest) {
|
if (sideloadedManifest) {
|
||||||
manifestLoaderErrorThrower = new LoaderErrorThrower.Dummy();
|
manifestLoaderErrorThrower = new LoaderErrorThrower.Dummy();
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ import com.google.android.exoplayer2.Player.VideoComponent;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.TextOutput;
|
import com.google.android.exoplayer2.text.TextOutput;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||||
|
|
@ -69,6 +70,7 @@ import com.google.android.exoplayer2.video.VideoListener;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -221,6 +223,11 @@ import java.util.List;
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Type: {@link PlayerControlView}
|
* <li>Type: {@link PlayerControlView}
|
||||||
* </ul>
|
* </ul>
|
||||||
|
* <li><b>{@code exo_ad_overlay}</b> - A {@link FrameLayout} positioned on top of the player which
|
||||||
|
* is used to show ad UI (if applicable).
|
||||||
|
* <ul>
|
||||||
|
* <li>Type: {@link FrameLayout}
|
||||||
|
* </ul>
|
||||||
* <li><b>{@code exo_overlay}</b> - A {@link FrameLayout} positioned on top of the player which
|
* <li><b>{@code exo_overlay}</b> - A {@link FrameLayout} positioned on top of the player which
|
||||||
* the app can access via {@link #getOverlayFrameLayout()}, provided for convenience.
|
* the app can access via {@link #getOverlayFrameLayout()}, provided for convenience.
|
||||||
* <ul>
|
* <ul>
|
||||||
|
|
@ -239,7 +246,7 @@ import java.util.List;
|
||||||
* PlayerView. This will cause the specified layout to be inflated instead of {@code
|
* PlayerView. This will cause the specified layout to be inflated instead of {@code
|
||||||
* exo_player_view.xml} for only the instance on which the attribute is set.
|
* exo_player_view.xml} for only the instance on which the attribute is set.
|
||||||
*/
|
*/
|
||||||
public class PlayerView extends FrameLayout {
|
public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider {
|
||||||
|
|
||||||
// LINT.IfChange
|
// LINT.IfChange
|
||||||
/**
|
/**
|
||||||
|
|
@ -278,9 +285,10 @@ public class PlayerView extends FrameLayout {
|
||||||
private final SubtitleView subtitleView;
|
private final SubtitleView subtitleView;
|
||||||
@Nullable private final View bufferingView;
|
@Nullable private final View bufferingView;
|
||||||
@Nullable private final TextView errorMessageView;
|
@Nullable private final TextView errorMessageView;
|
||||||
private final PlayerControlView controller;
|
@Nullable private final PlayerControlView controller;
|
||||||
private final ComponentListener componentListener;
|
private final ComponentListener componentListener;
|
||||||
private final FrameLayout overlayFrameLayout;
|
@Nullable private final FrameLayout adOverlayFrameLayout;
|
||||||
|
@Nullable private final FrameLayout overlayFrameLayout;
|
||||||
|
|
||||||
private Player player;
|
private Player player;
|
||||||
private boolean useController;
|
private boolean useController;
|
||||||
|
|
@ -317,6 +325,7 @@ public class PlayerView extends FrameLayout {
|
||||||
errorMessageView = null;
|
errorMessageView = null;
|
||||||
controller = null;
|
controller = null;
|
||||||
componentListener = null;
|
componentListener = null;
|
||||||
|
adOverlayFrameLayout = null;
|
||||||
overlayFrameLayout = null;
|
overlayFrameLayout = null;
|
||||||
ImageView logo = new ImageView(context);
|
ImageView logo = new ImageView(context);
|
||||||
if (Util.SDK_INT >= 23) {
|
if (Util.SDK_INT >= 23) {
|
||||||
|
|
@ -411,6 +420,9 @@ public class PlayerView extends FrameLayout {
|
||||||
surfaceView = null;
|
surfaceView = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ad overlay frame layout.
|
||||||
|
adOverlayFrameLayout = findViewById(R.id.exo_ad_overlay);
|
||||||
|
|
||||||
// Overlay frame layout.
|
// Overlay frame layout.
|
||||||
overlayFrameLayout = findViewById(R.id.exo_overlay);
|
overlayFrameLayout = findViewById(R.id.exo_overlay);
|
||||||
|
|
||||||
|
|
@ -1012,6 +1024,7 @@ public class PlayerView extends FrameLayout {
|
||||||
* @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and
|
* @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and
|
||||||
* the overlay is not present.
|
* the overlay is not present.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public FrameLayout getOverlayFrameLayout() {
|
public FrameLayout getOverlayFrameLayout() {
|
||||||
return overlayFrameLayout;
|
return overlayFrameLayout;
|
||||||
}
|
}
|
||||||
|
|
@ -1095,6 +1108,28 @@ public class PlayerView extends FrameLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdsLoader.AdViewProvider implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewGroup getAdViewGroup() {
|
||||||
|
return Assertions.checkNotNull(
|
||||||
|
adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View[] getAdOverlayViews() {
|
||||||
|
ArrayList<View> overlayViews = new ArrayList<>();
|
||||||
|
if (overlayFrameLayout != null) {
|
||||||
|
overlayViews.add(overlayFrameLayout);
|
||||||
|
}
|
||||||
|
if (controller != null) {
|
||||||
|
overlayViews.add(controller);
|
||||||
|
}
|
||||||
|
return overlayViews.toArray(new View[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
private boolean toggleControllerVisibility() {
|
private boolean toggleControllerVisibility() {
|
||||||
if (!useController || player == null) {
|
if (!useController || player == null) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,10 @@
|
||||||
|
|
||||||
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
|
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout android:id="@id/exo_ad_overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
<FrameLayout android:id="@id/exo_overlay"
|
<FrameLayout android:id="@id/exo_overlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent"/>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
<item name="exo_artwork" type="id"/>
|
<item name="exo_artwork" type="id"/>
|
||||||
<item name="exo_controller_placeholder" type="id"/>
|
<item name="exo_controller_placeholder" type="id"/>
|
||||||
<item name="exo_controller" type="id"/>
|
<item name="exo_controller" type="id"/>
|
||||||
|
<item name="exo_ad_overlay" type="id"/>
|
||||||
<item name="exo_overlay" type="id"/>
|
<item name="exo_overlay" type="id"/>
|
||||||
<item name="exo_play" type="id"/>
|
<item name="exo_play" type="id"/>
|
||||||
<item name="exo_pause" type="id"/>
|
<item name="exo_pause" type="id"/>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.Timeline.Period;
|
import com.google.android.exoplayer2.Timeline.Period;
|
||||||
|
|
@ -96,10 +95,7 @@ public class FakeMediaSource extends BaseMediaSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void prepareSourceInternal(
|
public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
ExoPlayer player,
|
|
||||||
boolean isTopLevelSource,
|
|
||||||
@Nullable TransferListener mediaTransferListener) {
|
|
||||||
assertThat(preparedSource).isFalse();
|
assertThat(preparedSource).isFalse();
|
||||||
transferListener = mediaTransferListener;
|
transferListener = mediaTransferListener;
|
||||||
preparedSource = true;
|
preparedSource = true;
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,13 @@ package com.google.android.exoplayer2.testutil;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import android.os.ConditionVariable;
|
import android.os.ConditionVariable;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
|
||||||
import com.google.android.exoplayer2.PlayerMessage;
|
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
|
@ -54,7 +50,6 @@ public class MediaSourceTestRunner {
|
||||||
|
|
||||||
public static final int TIMEOUT_MS = 10000;
|
public static final int TIMEOUT_MS = 10000;
|
||||||
|
|
||||||
private final StubExoPlayer player;
|
|
||||||
private final MediaSource mediaSource;
|
private final MediaSource mediaSource;
|
||||||
private final MediaSourceListener mediaSourceListener;
|
private final MediaSourceListener mediaSourceListener;
|
||||||
private final HandlerThread playbackThread;
|
private final HandlerThread playbackThread;
|
||||||
|
|
@ -79,7 +74,6 @@ public class MediaSourceTestRunner {
|
||||||
playbackThread.start();
|
playbackThread.start();
|
||||||
Looper playbackLooper = playbackThread.getLooper();
|
Looper playbackLooper = playbackThread.getLooper();
|
||||||
playbackHandler = new Handler(playbackLooper);
|
playbackHandler = new Handler(playbackLooper);
|
||||||
player = new EventHandlingExoPlayer(playbackLooper);
|
|
||||||
mediaSourceListener = new MediaSourceListener();
|
mediaSourceListener = new MediaSourceListener();
|
||||||
timelines = new LinkedBlockingDeque<>();
|
timelines = new LinkedBlockingDeque<>();
|
||||||
completedLoads = new CopyOnWriteArrayList<>();
|
completedLoads = new CopyOnWriteArrayList<>();
|
||||||
|
|
@ -121,11 +115,7 @@ public class MediaSourceTestRunner {
|
||||||
final IOException[] prepareError = new IOException[1];
|
final IOException[] prepareError = new IOException[1];
|
||||||
runOnPlaybackThread(
|
runOnPlaybackThread(
|
||||||
() -> {
|
() -> {
|
||||||
mediaSource.prepareSource(
|
mediaSource.prepareSource(mediaSourceListener, /* mediaTransferListener= */ null);
|
||||||
player,
|
|
||||||
/* isTopLevelSource= */ true,
|
|
||||||
mediaSourceListener,
|
|
||||||
/* mediaTransferListener= */ null);
|
|
||||||
try {
|
try {
|
||||||
// TODO: This only catches errors that are set synchronously in prepareSource. To
|
// TODO: This only catches errors that are set synchronously in prepareSource. To
|
||||||
// capture async errors we'll need to poll maybeThrowSourceInfoRefreshError until the
|
// capture async errors we'll need to poll maybeThrowSourceInfoRefreshError until the
|
||||||
|
|
@ -430,43 +420,4 @@ public class MediaSourceTestRunner {
|
||||||
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
|
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class EventHandlingExoPlayer extends StubExoPlayer
|
|
||||||
implements Handler.Callback, PlayerMessage.Sender {
|
|
||||||
|
|
||||||
private final Handler handler;
|
|
||||||
|
|
||||||
public EventHandlingExoPlayer(Looper looper) {
|
|
||||||
this.handler = new Handler(looper, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Looper getApplicationLooper() {
|
|
||||||
return handler.getLooper();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PlayerMessage createMessage(PlayerMessage.Target target) {
|
|
||||||
return new PlayerMessage(
|
|
||||||
/* sender= */ this, target, Timeline.EMPTY, /* defaultWindowIndex= */ 0, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(PlayerMessage message) {
|
|
||||||
handler.obtainMessage(0, message).sendToTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public boolean handleMessage(Message msg) {
|
|
||||||
PlayerMessage message = (PlayerMessage) msg.obj;
|
|
||||||
try {
|
|
||||||
message.getTarget().handleMessage(message.getType(), message.getPayload());
|
|
||||||
message.markAsProcessed(/* isDelivered= */ true);
|
|
||||||
} catch (ExoPlaybackException e) {
|
|
||||||
fail("Unexpected ExoPlaybackException.");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue