mirror of
https://github.com/samsonjs/media.git
synced 2026-04-10 12:05:47 +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.
|
||||
- Consult our FAQs, supported devices and supported formats pages. These can be
|
||||
found at https://google.github.io/ExoPlayer/.
|
||||
- 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.
|
||||
- This issue tracker is intended for bugs, feature requests and ExoPlayer
|
||||
specific questions. If you're asking a general Android development question,
|
||||
please do so on Stack Overflow.
|
||||
reproduce the issue 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 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
|
||||
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.
|
||||
|
||||
### Reproduction steps
|
||||
Describe how the issue can be reproduced, ideally using the ExoPlayer demo app.
|
||||
### [REQUIRED] Reproduction steps
|
||||
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
|
||||
Provide 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".
|
||||
### [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.
|
||||
|
||||
### Version of ExoPlayer being used
|
||||
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
|
||||
### [REQUIRED] A full bug report captured from the device
|
||||
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.
|
||||
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
|
||||
"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 #
|
||||
|
||||
### 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 ###
|
||||
|
||||
* HLS: Parse `CHANNELS` attribute from `EXT-X-MEDIA` tag.
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
// limitations under the License.
|
||||
project.ext {
|
||||
// ExoPlayer version and version code.
|
||||
releaseVersion = '2.9.5'
|
||||
releaseVersionCode = 2009005
|
||||
releaseVersion = '2.9.6'
|
||||
releaseVersionCode = 2009006
|
||||
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
|
||||
// components provided by the library may be of use on older devices.
|
||||
// However, please note that the core media playback functionality provided
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package="com.google.android.exoplayer2.imademo">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-sdk/>
|
||||
|
||||
<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.hls.HlsMediaSource;
|
||||
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.upstream.DataSource;
|
||||
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) {
|
||||
// Create a default track selector.
|
||||
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
|
||||
TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||
|
||||
// Create a player instance.
|
||||
player = ExoPlayerFactory.newSimpleInstance(context, trackSelector);
|
||||
|
||||
// Bind the player to the view.
|
||||
player = ExoPlayerFactory.newSimpleInstance(context);
|
||||
adsLoader.setPlayer(player);
|
||||
playerView.setPlayer(player);
|
||||
|
||||
// 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.
|
||||
MediaSource mediaSourceWithAds =
|
||||
new AdsMediaSource(
|
||||
contentMediaSource,
|
||||
/* adMediaSourceFactory= */ this,
|
||||
adsLoader,
|
||||
playerView.getOverlayFrameLayout());
|
||||
contentMediaSource, /* adMediaSourceFactory= */ this, adsLoader, playerView);
|
||||
|
||||
// Prepare the player with the source.
|
||||
player.seekTo(contentPosition);
|
||||
|
|
@ -89,6 +77,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||
contentPosition = player.getContentPosition();
|
||||
player.release();
|
||||
player = null;
|
||||
adsLoader.setPlayer(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,9 +27,7 @@ import android.util.Pair;
|
|||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
|
@ -151,7 +149,6 @@ public class PlayerActivity extends Activity
|
|||
|
||||
private AdsLoader adsLoader;
|
||||
private Uri loadedAdTagUri;
|
||||
private ViewGroup adUiViewGroup;
|
||||
|
||||
// Activity lifecycle
|
||||
|
||||
|
|
@ -474,7 +471,6 @@ public class PlayerActivity extends Activity
|
|||
return buildMediaSource(uri, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
|
||||
@ContentType int type = Util.inferContentType(uri, overrideExtension);
|
||||
switch (type) {
|
||||
|
|
@ -534,6 +530,9 @@ public class PlayerActivity extends Activity
|
|||
mediaSource = null;
|
||||
trackSelector = null;
|
||||
}
|
||||
if (adsLoader != null) {
|
||||
adsLoader.setPlayer(null);
|
||||
}
|
||||
releaseMediaDrm();
|
||||
}
|
||||
|
||||
|
|
@ -593,10 +592,8 @@ public class PlayerActivity extends Activity
|
|||
.getConstructor(android.content.Context.class, android.net.Uri.class);
|
||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||
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 =
|
||||
new AdsMediaSource.MediaSourceFactory() {
|
||||
@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 AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, adUiViewGroup);
|
||||
return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// IMA extension not loaded.
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import android.os.Looper;
|
|||
import android.os.SystemClock;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||
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.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
|
|
@ -73,7 +73,17 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
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
|
||||
implements Player.EventListener,
|
||||
AdsLoader,
|
||||
|
|
@ -92,9 +102,9 @@ public final class ImaAdsLoader
|
|||
|
||||
private final Context context;
|
||||
|
||||
private @Nullable ImaSdkSettings imaSdkSettings;
|
||||
private @Nullable AdEventListener adEventListener;
|
||||
private @Nullable Set<UiElement> adUiElements;
|
||||
@Nullable private ImaSdkSettings imaSdkSettings;
|
||||
@Nullable private AdEventListener adEventListener;
|
||||
@Nullable private Set<UiElement> adUiElements;
|
||||
private int vastLoadTimeoutMs;
|
||||
private int mediaLoadTimeoutMs;
|
||||
private int mediaBitrate;
|
||||
|
|
@ -316,10 +326,11 @@ public final class ImaAdsLoader
|
|||
private final AdDisplayContainer adDisplayContainer;
|
||||
private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
|
||||
|
||||
@Nullable private Player nextPlayer;
|
||||
private Object pendingAdRequestContext;
|
||||
private List<String> supportedMimeTypes;
|
||||
private EventListener eventListener;
|
||||
private Player player;
|
||||
@Nullable private EventListener eventListener;
|
||||
@Nullable private Player player;
|
||||
private VideoProgressUpdate lastContentProgress;
|
||||
private VideoProgressUpdate lastAdProgress;
|
||||
private int lastVolumePercentage;
|
||||
|
|
@ -459,11 +470,11 @@ public final class ImaAdsLoader
|
|||
}
|
||||
imaSdkSettings.setPlayerType(IMA_SDK_SETTINGS_PLAYER_TYPE);
|
||||
imaSdkSettings.setPlayerVersion(IMA_SDK_SETTINGS_PLAYER_VERSION);
|
||||
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings);
|
||||
period = new Timeline.Period();
|
||||
adCallbacks = new ArrayList<>(/* initialCapacity= */ 1);
|
||||
adDisplayContainer = imaFactory.createAdDisplayContainer();
|
||||
adDisplayContainer.setPlayer(/* videoAdPlayer= */ this);
|
||||
adsLoader = imaFactory.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
|
||||
adsLoader.addAdErrorListener(/* adErrorListener= */ this);
|
||||
adsLoader.addAdsLoadedListener(/* adsLoadedListener= */ this);
|
||||
fakeContentProgressElapsedRealtimeMs = C.TIME_UNSET;
|
||||
|
|
@ -481,13 +492,29 @@ public final class ImaAdsLoader
|
|||
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
|
||||
* ImaSdkFactory#createCompanionAdSlot()}.
|
||||
*
|
||||
* @param companionSlots Slots for displaying companion ads.
|
||||
* @see AdDisplayContainer#setCompanionSlots(Collection)
|
||||
* @deprecated Use {@code getAdDisplayContainer().setCompanionSlots(...)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCompanionSlots(Collection<CompanionAdSlot> 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
|
||||
* 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) {
|
||||
// Ads have already been requested.
|
||||
return;
|
||||
}
|
||||
adDisplayContainer.setAdContainer(adUiViewGroup);
|
||||
adDisplayContainer.setAdContainer(adViewGroup);
|
||||
pendingAdRequestContext = new Object();
|
||||
AdsRequest request = imaFactory.createAdsRequest();
|
||||
if (adTagUri != null) {
|
||||
|
|
@ -517,7 +544,6 @@ public final class ImaAdsLoader
|
|||
if (vastLoadTimeoutMs != TIMEOUT_UNSET) {
|
||||
request.setVastLoadTimeout(vastLoadTimeoutMs);
|
||||
}
|
||||
request.setAdDisplayContainer(adDisplayContainer);
|
||||
request.setContentProgressProvider(this);
|
||||
request.setUserRequestContext(pendingAdRequestContext);
|
||||
adsLoader.requestAds(request);
|
||||
|
|
@ -525,6 +551,14 @@ public final class ImaAdsLoader
|
|||
|
||||
// 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
|
||||
public void setSupportedContentTypes(@C.ContentType int... contentTypes) {
|
||||
List<String> supportedMimeTypes = new ArrayList<>();
|
||||
|
|
@ -549,14 +583,20 @@ public final class ImaAdsLoader
|
|||
}
|
||||
|
||||
@Override
|
||||
public void attachPlayer(ExoPlayer player, EventListener eventListener, ViewGroup adUiViewGroup) {
|
||||
Assertions.checkArgument(player.getApplicationLooper() == Looper.getMainLooper());
|
||||
this.player = player;
|
||||
public void start(EventListener eventListener, AdViewProvider adViewProvider) {
|
||||
Assertions.checkNotNull(
|
||||
nextPlayer, "Set player using adsLoader.setPlayer before preparing the player.");
|
||||
player = nextPlayer;
|
||||
this.eventListener = eventListener;
|
||||
lastVolumePercentage = 0;
|
||||
lastAdProgress = 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);
|
||||
maybeNotifyPendingAdLoadError();
|
||||
if (adPlaybackState != null) {
|
||||
|
|
@ -570,12 +610,12 @@ public final class ImaAdsLoader
|
|||
startAdPlayback();
|
||||
} else {
|
||||
// Ads haven't loaded yet, so request them.
|
||||
requestAds(adUiViewGroup);
|
||||
requestAds(adViewGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachPlayer() {
|
||||
public void stop() {
|
||||
if (adsManager != null && imaPausedContent) {
|
||||
adPlaybackState =
|
||||
adPlaybackState.withAdResumePositionUs(
|
||||
|
|
@ -585,6 +625,7 @@ public final class ImaAdsLoader
|
|||
lastVolumePercentage = getVolume();
|
||||
lastAdProgress = getAdProgress();
|
||||
lastContentProgress = getContentProgress();
|
||||
adDisplayContainer.unregisterAllVideoControlsOverlays();
|
||||
player.removeListener(this);
|
||||
player = null;
|
||||
eventListener = null;
|
||||
|
|
@ -1331,7 +1372,8 @@ public final class ImaAdsLoader
|
|||
private static boolean isAdGroupLoadError(AdError adError) {
|
||||
// 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.
|
||||
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) {
|
||||
|
|
@ -1357,9 +1399,9 @@ public final class ImaAdsLoader
|
|||
AdDisplayContainer createAdDisplayContainer();
|
||||
/** @see com.google.ads.interactivemedia.v3.api.ImaSdkFactory#createAdsRequest() */
|
||||
AdsRequest createAdsRequest();
|
||||
/** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings) */
|
||||
/** @see ImaSdkFactory#createAdsLoader(Context, ImaSdkSettings, AdDisplayContainer) */
|
||||
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}. */
|
||||
|
|
@ -1386,8 +1428,9 @@ public final class ImaAdsLoader
|
|||
|
||||
@Override
|
||||
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
||||
Context context, ImaSdkSettings imaSdkSettings) {
|
||||
return ImaSdkFactory.getInstance().createAdsLoader(context, imaSdkSettings);
|
||||
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
|
||||
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.Timeline;
|
||||
import com.google.android.exoplayer2.testutil.StubExoPlayer;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** A fake player for testing content/ad playback. */
|
||||
|
|
@ -109,6 +110,11 @@ import java.util.ArrayList;
|
|||
|
||||
// ExoPlayer methods. Other methods are unsupported.
|
||||
|
||||
@Override
|
||||
public AudioComponent getAudioComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Looper getApplicationLooper() {
|
||||
return Looper.getMainLooper();
|
||||
|
|
@ -134,6 +140,16 @@ import java.util.ArrayList;
|
|||
return playWhenReady;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRendererCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
return new TrackSelectionArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline getCurrentTimeline() {
|
||||
return timeline;
|
||||
|
|
|
|||
|
|
@ -17,11 +17,13 @@ package com.google.android.exoplayer2.ext.ima;
|
|||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import com.google.ads.interactivemedia.v3.api.Ad;
|
||||
|
|
@ -49,6 +51,7 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
|
@ -73,7 +76,9 @@ public class ImaAdsLoaderTest {
|
|||
private @Mock AdDisplayContainer adDisplayContainer;
|
||||
private @Mock AdsManager adsManager;
|
||||
private SingletonImaFactory testImaFactory;
|
||||
private ViewGroup adUiViewGroup;
|
||||
private ViewGroup adViewGroup;
|
||||
private View adOverlayView;
|
||||
private AdsLoader.AdViewProvider adViewProvider;
|
||||
private TestAdsLoaderListener adsLoaderListener;
|
||||
private FakePlayer fakeExoPlayer;
|
||||
private ImaAdsLoader imaAdsLoader;
|
||||
|
|
@ -90,7 +95,20 @@ public class ImaAdsLoaderTest {
|
|||
adDisplayContainer,
|
||||
fakeAdsRequest,
|
||||
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
|
||||
|
|
@ -109,17 +127,18 @@ public class ImaAdsLoaderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAttachPlayer_setsAdUiViewGroup() {
|
||||
public void testStart_setsAdUiViewGroup() {
|
||||
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
|
||||
public void testAttachPlayer_updatesAdPlaybackState() {
|
||||
public void testStart_updatesAdPlaybackState() {
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
|
||||
assertThat(adsLoaderListener.adPlaybackState)
|
||||
.isEqualTo(
|
||||
|
|
@ -128,17 +147,17 @@ public class ImaAdsLoaderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testAttachAfterRelease() {
|
||||
public void testStartAfterRelease() {
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.release();
|
||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttachAndCallbacksAfterRelease() {
|
||||
public void testStartAndCallbacksAfterRelease() {
|
||||
setupPlayback(CONTENT_TIMELINE, PREROLL_ADS_DURATIONS_US, PREROLL_CUE_POINTS_SECONDS);
|
||||
imaAdsLoader.release();
|
||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
fakeExoPlayer.setPlayingContentPosition(/* position= */ 0);
|
||||
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
|
||||
// when using Robolectric and accessing VideoProgressUpdate.VIDEO_TIME_NOT_READY, due to the IMA
|
||||
// SDK being proguarded.
|
||||
imaAdsLoader.requestAds(adUiViewGroup);
|
||||
imaAdsLoader.requestAds(adViewGroup);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
|
||||
imaAdsLoader.loadAd(TEST_URI.toString());
|
||||
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);
|
||||
|
||||
// Load the preroll ad.
|
||||
imaAdsLoader.attachPlayer(fakeExoPlayer, adsLoaderListener, adUiViewGroup);
|
||||
imaAdsLoader.start(adsLoaderListener, adViewProvider);
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.LOADED, UNSKIPPABLE_AD));
|
||||
imaAdsLoader.loadAd(TEST_URI.toString());
|
||||
imaAdsLoader.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, UNSKIPPABLE_AD));
|
||||
|
|
@ -201,6 +220,18 @@ public class ImaAdsLoaderTest {
|
|||
.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) {
|
||||
fakeExoPlayer = new FakePlayer();
|
||||
adsLoaderListener = new TestAdsLoaderListener(fakeExoPlayer, contentTimeline, adDurationsUs);
|
||||
|
|
@ -210,6 +241,7 @@ public class ImaAdsLoaderTest {
|
|||
.setImaFactory(testImaFactory)
|
||||
.setImaSdkSettings(imaSdkSettings)
|
||||
.buildForAdTag(TEST_URI);
|
||||
imaAdsLoader.setPlayer(fakeExoPlayer);
|
||||
}
|
||||
|
||||
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 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.AdsRequest;
|
||||
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
|
||||
|
|
@ -64,8 +65,8 @@ final class SingletonImaFactory implements ImaAdsLoader.ImaFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public com.google.ads.interactivemedia.v3.api.AdsLoader createAdsLoader(
|
||||
Context context, ImaSdkSettings imaSdkSettings) {
|
||||
public AdsLoader createAdsLoader(
|
||||
Context context, ImaSdkSettings imaSdkSettings, AdDisplayContainer adDisplayContainer) {
|
||||
return adsLoader;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
repeatMode,
|
||||
shuffleModeEnabled,
|
||||
eventHandler,
|
||||
this,
|
||||
clock);
|
||||
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ import java.util.Collections;
|
|||
private final HandlerWrapper handler;
|
||||
private final HandlerThread internalPlaybackThread;
|
||||
private final Handler eventHandler;
|
||||
private final ExoPlayer player;
|
||||
private final Timeline.Window window;
|
||||
private final Timeline.Period period;
|
||||
private final long backBufferDurationUs;
|
||||
|
|
@ -131,7 +130,6 @@ import java.util.Collections;
|
|||
@Player.RepeatMode int repeatMode,
|
||||
boolean shuffleModeEnabled,
|
||||
Handler eventHandler,
|
||||
ExoPlayer player,
|
||||
Clock clock) {
|
||||
this.renderers = renderers;
|
||||
this.trackSelector = trackSelector;
|
||||
|
|
@ -142,7 +140,6 @@ import java.util.Collections;
|
|||
this.repeatMode = repeatMode;
|
||||
this.shuffleModeEnabled = shuffleModeEnabled;
|
||||
this.eventHandler = eventHandler;
|
||||
this.player = player;
|
||||
this.clock = clock;
|
||||
this.queue = new MediaPeriodQueue();
|
||||
|
||||
|
|
@ -398,11 +395,7 @@ import java.util.Collections;
|
|||
loadControl.onPrepared();
|
||||
this.mediaSource = mediaSource;
|
||||
setState(Player.STATE_BUFFERING);
|
||||
mediaSource.prepareSource(
|
||||
player,
|
||||
/* isTopLevelSource= */ true,
|
||||
/* listener= */ this,
|
||||
bandwidthMeter.getTransferListener());
|
||||
mediaSource.prepareSource(/* listener= */ this, bandwidthMeter.getTransferListener());
|
||||
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". */
|
||||
// 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}. */
|
||||
// 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.
|
||||
|
|
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
|
|||
* integer version 123045006 (123-045-006).
|
||||
*/
|
||||
// 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}
|
||||
|
|
|
|||
|
|
@ -591,6 +591,14 @@ public final class FragmentedMp4Extractor implements Extractor {
|
|||
long presentationTimeDeltaUs =
|
||||
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.
|
||||
for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
|
||||
atom.setPosition(Atom.FULL_HEADER_SIZE);
|
||||
|
|
|
|||
|
|
@ -44,13 +44,6 @@ public final class EventMessage implements Metadata.Entry {
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
|
|
@ -70,22 +63,19 @@ public final class EventMessage implements Metadata.Entry {
|
|||
* @param durationMs The duration of the event in milliseconds.
|
||||
* @param id The instance identifier.
|
||||
* @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,
|
||||
byte[] messageData, long presentationTimeUs) {
|
||||
public EventMessage(
|
||||
String schemeIdUri, String value, long durationMs, long id, byte[] messageData) {
|
||||
this.schemeIdUri = schemeIdUri;
|
||||
this.value = value;
|
||||
this.durationMs = durationMs;
|
||||
this.id = id;
|
||||
this.messageData = messageData;
|
||||
this.presentationTimeUs = presentationTimeUs;
|
||||
}
|
||||
|
||||
/* package */ EventMessage(Parcel in) {
|
||||
schemeIdUri = castNonNull(in.readString());
|
||||
value = castNonNull(in.readString());
|
||||
presentationTimeUs = in.readLong();
|
||||
durationMs = in.readLong();
|
||||
id = in.readLong();
|
||||
messageData = castNonNull(in.createByteArray());
|
||||
|
|
@ -97,7 +87,6 @@ public final class EventMessage implements Metadata.Entry {
|
|||
int result = 17;
|
||||
result = 31 * result + (schemeIdUri != null ? schemeIdUri.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) (id ^ (id >>> 32));
|
||||
result = 31 * result + Arrays.hashCode(messageData);
|
||||
|
|
@ -115,9 +104,11 @@ public final class EventMessage implements Metadata.Entry {
|
|||
return false;
|
||||
}
|
||||
EventMessage other = (EventMessage) obj;
|
||||
return presentationTimeUs == other.presentationTimeUs && durationMs == other.durationMs
|
||||
&& id == other.id && Util.areEqual(schemeIdUri, other.schemeIdUri)
|
||||
&& Util.areEqual(value, other.value) && Arrays.equals(messageData, other.messageData);
|
||||
return durationMs == other.durationMs
|
||||
&& id == other.id
|
||||
&& Util.areEqual(schemeIdUri, other.schemeIdUri)
|
||||
&& Util.areEqual(value, other.value)
|
||||
&& Arrays.equals(messageData, other.messageData);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -136,7 +127,6 @@ public final class EventMessage implements Metadata.Entry {
|
|||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(schemeIdUri);
|
||||
dest.writeString(value);
|
||||
dest.writeLong(presentationTimeUs);
|
||||
dest.writeLong(durationMs);
|
||||
dest.writeLong(id);
|
||||
dest.writeByteArray(messageData);
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@
|
|||
*/
|
||||
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.MetadataDecoder;
|
||||
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
|
||||
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.Util;
|
||||
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.
|
||||
* <p>
|
||||
* Atom data should be provided to the decoder without the full atom header (i.e. starting from the
|
||||
* first byte of the scheme_id_uri field).
|
||||
*
|
||||
* <p>Atom data should be provided to the decoder without the full atom header (i.e. starting from
|
||||
* 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 {
|
||||
|
||||
private static final String TAG = "EventMessageDecoder";
|
||||
|
||||
@SuppressWarnings("ByteBufferBackingArray")
|
||||
@Override
|
||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||
|
|
@ -43,13 +46,16 @@ public final class EventMessageDecoder implements MetadataDecoder {
|
|||
String schemeIdUri = Assertions.checkNotNull(emsgData.readNullTerminatedString());
|
||||
String value = Assertions.checkNotNull(emsgData.readNullTerminatedString());
|
||||
long timescale = emsgData.readUnsignedInt();
|
||||
long presentationTimeUs = Util.scaleLargeTimestamp(emsgData.readUnsignedInt(),
|
||||
C.MICROS_PER_SECOND, timescale);
|
||||
long presentationTimeDelta = emsgData.readUnsignedInt();
|
||||
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 id = emsgData.readUnsignedInt();
|
||||
byte[] messageData = Arrays.copyOfRange(data, emsgData.getPosition(), size);
|
||||
return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData,
|
||||
presentationTimeUs));
|
||||
return new Metadata(new EventMessage(schemeIdUri, value, durationMs, id, messageData));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@
|
|||
package com.google.android.exoplayer2.metadata.emsg;
|
||||
|
||||
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.DataOutputStream;
|
||||
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
|
||||
* {@link EventMessageDecoder}.
|
||||
* Encodes an {@link EventMessage} to a byte array that can be decoded by {@link
|
||||
* EventMessageDecoder}.
|
||||
*
|
||||
* @param eventMessage The event message to be encoded.
|
||||
* @param timescale Timescale of the event message, in units per second.
|
||||
* @return The serialized byte array.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] encode(EventMessage eventMessage, long timescale) {
|
||||
Assertions.checkArgument(timescale >= 0);
|
||||
public byte[] encode(EventMessage eventMessage) {
|
||||
byteArrayOutputStream.reset();
|
||||
try {
|
||||
writeNullTerminatedString(dataOutputStream, eventMessage.schemeIdUri);
|
||||
String nonNullValue = eventMessage.value != null ? eventMessage.value : "";
|
||||
writeNullTerminatedString(dataOutputStream, nonNullValue);
|
||||
writeUnsignedInt(dataOutputStream, timescale);
|
||||
long presentationTime = Util.scaleLargeTimestamp(eventMessage.presentationTimeUs,
|
||||
timescale, C.MICROS_PER_SECOND);
|
||||
writeUnsignedInt(dataOutputStream, presentationTime);
|
||||
long duration = Util.scaleLargeTimestamp(eventMessage.durationMs, timescale, 1000);
|
||||
writeUnsignedInt(dataOutputStream, duration);
|
||||
writeUnsignedInt(dataOutputStream, 1000); // timescale
|
||||
writeUnsignedInt(dataOutputStream, 0); // presentation_time_delta
|
||||
writeUnsignedInt(dataOutputStream, eventMessage.durationMs);
|
||||
writeUnsignedInt(dataOutputStream, eventMessage.id);
|
||||
dataOutputStream.write(eventMessage.messageData);
|
||||
dataOutputStream.flush();
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
package com.google.android.exoplayer2.source;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
|
|
@ -35,9 +35,9 @@ public abstract class BaseMediaSource implements MediaSource {
|
|||
private final ArrayList<SourceInfoRefreshListener> sourceInfoListeners;
|
||||
private final MediaSourceEventListener.EventDispatcher eventDispatcher;
|
||||
|
||||
private @Nullable ExoPlayer player;
|
||||
private @Nullable Timeline timeline;
|
||||
private @Nullable Object manifest;
|
||||
@Nullable private Looper looper;
|
||||
@Nullable private Timeline timeline;
|
||||
@Nullable private Object manifest;
|
||||
|
||||
public BaseMediaSource() {
|
||||
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
|
||||
* #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
|
||||
* 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
|
||||
* manifests and other data.
|
||||
*/
|
||||
protected abstract void prepareSourceInternal(
|
||||
ExoPlayer player, boolean isTopLevelSource, @Nullable TransferListener mediaTransferListener);
|
||||
protected abstract void prepareSourceInternal(@Nullable TransferListener mediaTransferListener);
|
||||
|
||||
/**
|
||||
* Releases the source. This method is called exactly once after each call to {@link
|
||||
* #prepareSourceInternal(ExoPlayer, boolean, TransferListener)}.
|
||||
* #prepareSourceInternal(TransferListener)}.
|
||||
*/
|
||||
protected abstract void releaseSourceInternal();
|
||||
|
||||
|
|
@ -135,21 +130,14 @@ public abstract class BaseMediaSource implements MediaSource {
|
|||
|
||||
@Override
|
||||
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,
|
||||
@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);
|
||||
if (this.player == null) {
|
||||
this.player = player;
|
||||
prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
||||
if (this.looper == null) {
|
||||
this.looper = looper;
|
||||
prepareSourceInternal(mediaTransferListener);
|
||||
} else if (timeline != null) {
|
||||
listener.onSourceInfoRefreshed(/* source= */ this, timeline, manifest);
|
||||
}
|
||||
|
|
@ -159,7 +147,7 @@ public abstract class BaseMediaSource implements MediaSource {
|
|||
public final void releaseSource(SourceInfoRefreshListener listener) {
|
||||
sourceInfoListeners.remove(listener);
|
||||
if (sourceInfoListeners.isEmpty()) {
|
||||
player = null;
|
||||
looper = null;
|
||||
timeline = null;
|
||||
manifest = null;
|
||||
releaseSourceInternal();
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
|||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
|
@ -223,11 +222,8 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(mediaTransferListener);
|
||||
prepareChildSource(/* id= */ null, mediaSource);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
|||
import android.os.Handler;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
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 @Nullable ExoPlayer player;
|
||||
private @Nullable Handler eventHandler;
|
||||
private @Nullable TransferListener mediaTransferListener;
|
||||
|
||||
|
|
@ -46,11 +44,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
|||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
this.player = player;
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
this.mediaTransferListener = mediaTransferListener;
|
||||
eventHandler = new Handler();
|
||||
}
|
||||
|
|
@ -71,7 +65,6 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
|||
childSource.mediaSource.removeEventListener(childSource.eventListener);
|
||||
}
|
||||
childSources.clear();
|
||||
player = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -105,11 +98,7 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
|
|||
MediaSourceEventListener eventListener = new ForwardingEventListener(id);
|
||||
childSources.put(id, new MediaSourceAndListener(mediaSource, sourceListener, eventListener));
|
||||
mediaSource.addEventListener(Assertions.checkNotNull(eventHandler), eventListener);
|
||||
mediaSource.prepareSource(
|
||||
Assertions.checkNotNull(player),
|
||||
/* isTopLevelSource= */ false,
|
||||
sourceListener,
|
||||
mediaTransferListener);
|
||||
mediaSource.prepareSource(sourceListener, mediaTransferListener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder;
|
||||
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
|
||||
|
|
@ -428,10 +427,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
|||
|
||||
@Override
|
||||
public final synchronized void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
||||
super.prepareSourceInternal(mediaTransferListener);
|
||||
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
|
||||
if (mediaSourcesPublic.isEmpty()) {
|
||||
updateTimelineAndScheduleOnCompletionActions();
|
||||
|
|
@ -1163,10 +1160,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
|
|||
private static final class DummyMediaSource extends BaseMediaSource {
|
||||
|
||||
@Override
|
||||
protected void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import android.net.Uri;
|
|||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
|
|
@ -365,10 +364,7 @@ public final class ExtractorMediaSource extends BaseMediaSource
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
transferListener = mediaTransferListener;
|
||||
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,11 +71,8 @@ public final class LoopingMediaSource extends CompositeMediaSource<Void> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(mediaTransferListener);
|
||||
prepareChildSource(/* id= */ null, childSource);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,23 +25,24 @@ import com.google.android.exoplayer2.upstream.TransferListener;
|
|||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Defines and provides media to be played by an {@link ExoPlayer}. A MediaSource has two main
|
||||
* responsibilities:
|
||||
* Defines and provides media to be played by an {@link com.google.android.exoplayer2.ExoPlayer}. A
|
||||
* MediaSource has two main responsibilities:
|
||||
*
|
||||
* <ul>
|
||||
* <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
|
||||
* provides these timelines by calling {@link SourceInfoRefreshListener#onSourceInfoRefreshed}
|
||||
* on the {@link SourceInfoRefreshListener}s passed to {@link #prepareSource(ExoPlayer,
|
||||
* boolean, SourceInfoRefreshListener, TransferListener)}.
|
||||
* on the {@link SourceInfoRefreshListener}s passed to {@link
|
||||
* #prepareSource(SourceInfoRefreshListener, TransferListener)}.
|
||||
* <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
|
||||
* way for the player to load and read the media.
|
||||
* </ul>
|
||||
*
|
||||
* 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
|
||||
* re-used, but only for one {@link ExoPlayer} instance simultaneously.
|
||||
* com.google.android.exoplayer2.ExoPlayer} Javadoc. They should not be called directly from
|
||||
* application code. Instances can be re-used, but only for one {@link
|
||||
* com.google.android.exoplayer2.ExoPlayer} instance simultaneously.
|
||||
*/
|
||||
public interface MediaSource {
|
||||
|
||||
|
|
@ -226,11 +227,6 @@ public interface MediaSource {
|
|||
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
|
||||
* updates.
|
||||
|
|
@ -242,11 +238,6 @@ public interface MediaSource {
|
|||
* <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.
|
||||
*
|
||||
* @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 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
|
||||
|
|
@ -254,10 +245,7 @@ public interface MediaSource {
|
|||
* and other data.
|
||||
*/
|
||||
void prepareSource(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
SourceInfoRefreshListener listener,
|
||||
@Nullable TransferListener mediaTransferListener);
|
||||
SourceInfoRefreshListener listener, @Nullable TransferListener mediaTransferListener);
|
||||
|
||||
/**
|
||||
* 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.Nullable;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
|
|
@ -105,11 +104,8 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(mediaTransferListener);
|
||||
for (int i = 0; i < mediaSources.length; i++) {
|
||||
prepareChildSource(i, mediaSources[i]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package com.google.android.exoplayer2.source;
|
|||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
|
@ -304,10 +303,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
transferListener = mediaTransferListener;
|
||||
refreshSourceInfo(timeline, /* manifest= */ null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,11 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.source.ads;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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.upstream.DataSpec;
|
||||
import java.io.IOException;
|
||||
|
|
@ -25,27 +27,25 @@ import java.io.IOException;
|
|||
/**
|
||||
* 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)}
|
||||
* 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).
|
||||
*
|
||||
* <p>{@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} will be called when the ads media
|
||||
* source first initializes, at which point the loader can request ads. If the player enters the
|
||||
* background, {@link #detachPlayer()} will be called. Loaders should maintain any ad playback state
|
||||
* in preparation for a later call to {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. If
|
||||
* an ad is playing when the player is detached, update the ad playback state with the current
|
||||
* playback position using {@link AdPlaybackState#withAdResumePositionUs(long)}.
|
||||
* <p>{@link #start(EventListener, AdViewProvider)} will be called when the ads media source first
|
||||
* initializes, at which point the loader can request ads. If the player enters the background,
|
||||
* {@link #stop()} will be called. Loaders should maintain any ad playback state in preparation for
|
||||
* a later call to {@link #start(EventListener, AdViewProvider)}. If an ad is playing when the
|
||||
* player is detached, update the ad playback state with the current playback position using {@link
|
||||
* AdPlaybackState#withAdResumePositionUs(long)}.
|
||||
*
|
||||
* <p>If {@link EventListener#onAdPlaybackState(AdPlaybackState)} has been called, the
|
||||
* implementation of {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)} should invoke the
|
||||
* same listener to provide the existing playback state to the new player.
|
||||
* implementation of {@link #start(EventListener, AdViewProvider)} should invoke the same listener
|
||||
* to provide the existing playback state to the new player.
|
||||
*/
|
||||
public interface AdsLoader {
|
||||
|
||||
/**
|
||||
* Listener for ad loader events. All methods are called on the main thread.
|
||||
*/
|
||||
/** Listener for ads loader events. All methods are called on the main thread. */
|
||||
interface EventListener {
|
||||
|
||||
/**
|
||||
|
|
@ -53,7 +53,7 @@ public interface AdsLoader {
|
|||
*
|
||||
* @param adPlaybackState The new ad playback state.
|
||||
*/
|
||||
void onAdPlaybackState(AdPlaybackState adPlaybackState);
|
||||
default void onAdPlaybackState(AdPlaybackState adPlaybackState) {}
|
||||
|
||||
/**
|
||||
* Called when there was an error loading ads.
|
||||
|
|
@ -61,23 +61,62 @@ public interface AdsLoader {
|
|||
* @param error The 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).
|
||||
*/
|
||||
void onAdClicked();
|
||||
|
||||
/**
|
||||
* Called when the user taps a non-clickthrough part of an ad.
|
||||
*/
|
||||
void onAdTapped();
|
||||
/** Called when the user clicks through an ad (for example, following a 'learn more' link). */
|
||||
default void onAdClicked() {}
|
||||
|
||||
/** 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
|
||||
* {@link #attachPlayer(ExoPlayer, EventListener, ViewGroup)}. Subsequent calls may be ignored.
|
||||
* Sets the player that will play the loaded ads.
|
||||
*
|
||||
* <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
|
||||
* {@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);
|
||||
|
||||
/**
|
||||
* Attaches a player that will play ads loaded using this instance. Called on the main thread by
|
||||
* {@link AdsMediaSource}.
|
||||
* Starts using the ads loader for playback. Called on the main thread by {@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 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
|
||||
* {@link AdsMediaSource}.
|
||||
* Stops using the ads loader for playback and deregisters the event listener. Called on the main
|
||||
* thread by {@link AdsMediaSource}.
|
||||
*/
|
||||
void detachPlayer();
|
||||
|
||||
/**
|
||||
* Releases the loader. Called by the application on the main thread when the instance is no
|
||||
* longer needed.
|
||||
*/
|
||||
void release();
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Called on the main thread by {@link AdsMediaSource}.
|
||||
*
|
||||
* @param adGroupIndex The index of 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.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.ViewGroup;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.source.CompositeMediaSource;
|
||||
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.
|
||||
private static final MediaPeriodId DUMMY_CONTENT_MEDIA_PERIOD_ID =
|
||||
new MediaPeriodId(/* periodUid= */ new Object());
|
||||
|
|
@ -187,9 +145,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
private final MediaSource contentMediaSource;
|
||||
private final MediaSourceFactory adMediaSourceFactory;
|
||||
private final AdsLoader adsLoader;
|
||||
private final ViewGroup adUiViewGroup;
|
||||
@Nullable private final Handler eventHandler;
|
||||
@Nullable private final EventListener eventListener;
|
||||
private final AdsLoader.AdViewProvider adViewProvider;
|
||||
private final Handler mainHandler;
|
||||
private final Map<MediaSource, List<DeferredMediaPeriod>> deferredMediaPeriodByAdMediaSource;
|
||||
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 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 adViewProvider Provider of views for the ad UI.
|
||||
*/
|
||||
public AdsMediaSource(
|
||||
MediaSource contentMediaSource,
|
||||
DataSource.Factory dataSourceFactory,
|
||||
AdsLoader adsLoader,
|
||||
ViewGroup adUiViewGroup) {
|
||||
AdsLoader.AdViewProvider adViewProvider) {
|
||||
this(
|
||||
contentMediaSource,
|
||||
new ExtractorMediaSource.Factory(dataSourceFactory),
|
||||
adsLoader,
|
||||
adUiViewGroup,
|
||||
/* eventHandler= */ null,
|
||||
/* eventListener= */ null);
|
||||
adViewProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -232,85 +186,17 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
* @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 adViewProvider Provider of views for the ad UI.
|
||||
*/
|
||||
public AdsMediaSource(
|
||||
MediaSource contentMediaSource,
|
||||
MediaSourceFactory adMediaSourceFactory,
|
||||
AdsLoader adsLoader,
|
||||
ViewGroup adUiViewGroup) {
|
||||
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) {
|
||||
AdsLoader.AdViewProvider adViewProvider) {
|
||||
this.contentMediaSource = contentMediaSource;
|
||||
this.adMediaSourceFactory = adMediaSourceFactory;
|
||||
this.adsLoader = adsLoader;
|
||||
this.adUiViewGroup = adUiViewGroup;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.adViewProvider = adViewProvider;
|
||||
mainHandler = new Handler(Looper.getMainLooper());
|
||||
deferredMediaPeriodByAdMediaSource = new HashMap<>();
|
||||
period = new Timeline.Period();
|
||||
|
|
@ -326,18 +212,12 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
final ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@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();
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
super.prepareSourceInternal(mediaTransferListener);
|
||||
ComponentListener componentListener = new ComponentListener();
|
||||
this.componentListener = componentListener;
|
||||
prepareChildSource(DUMMY_CONTENT_MEDIA_PERIOD_ID, contentMediaSource);
|
||||
mainHandler.post(() -> adsLoader.attachPlayer(player, componentListener, adUiViewGroup));
|
||||
mainHandler.post(() -> adsLoader.start(componentListener, adViewProvider));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -406,7 +286,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
adPlaybackState = null;
|
||||
adGroupMediaSources = new MediaSource[0][];
|
||||
adGroupTimelines = new Timeline[0][];
|
||||
mainHandler.post(adsLoader::detachPlayer);
|
||||
mainHandler.post(adsLoader::stop);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -446,6 +326,7 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
}
|
||||
|
||||
private void onContentSourceInfoRefreshed(Timeline timeline, Object manifest) {
|
||||
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
||||
contentTimeline = timeline;
|
||||
contentManifest = manifest;
|
||||
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
|
||||
public void onAdLoadError(final AdLoadException error, DataSpec dataSpec) {
|
||||
if (released) {
|
||||
|
|
@ -574,18 +425,6 @@ public final class AdsMediaSource extends CompositeMediaSource<MediaPeriodId> {
|
|||
/* bytesLoaded= */ 0,
|
||||
error,
|
||||
/* 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 {
|
||||
int maxWidth = format.width;
|
||||
int maxHeight = format.height;
|
||||
if (codecNeedsMaxVideoSizeResetWorkaround(codecInfo.name)) {
|
||||
maxWidth = Math.max(maxWidth, 1920);
|
||||
maxHeight = Math.max(maxHeight, 1089);
|
||||
}
|
||||
int maxInputSize = getMaxInputSize(codecInfo, format);
|
||||
if (streamFormats.length == 1) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
*
|
||||
|
|
|
|||
|
|
@ -267,10 +267,8 @@ public final class ExoPlayerTest {
|
|||
new FakeMediaSource(timeline, new Object(), Builder.VIDEO_FORMAT) {
|
||||
@Override
|
||||
public synchronized void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@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
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ public final class EventMessageDecoderTest {
|
|||
assertThat(eventMessage.durationMs).isEqualTo(3000);
|
||||
assertThat(eventMessage.id).isEqualTo(1000403);
|
||||
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
|
||||
public void testEncodeEventStream() throws IOException {
|
||||
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
|
||||
new byte[] {0, 1, 2, 3, 4}, 1000000);
|
||||
byte[] expectedEmsgBody = new byte[] {
|
||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||
49, 50, 51, 0, // value = "123"
|
||||
0, 0, -69, -128, // timescale = 48000
|
||||
0, 0, -69, -128, // presentation_time_delta = 48000
|
||||
0, 2, 50, -128, // event_duration = 144000
|
||||
0, 15, 67, -45, // id = 1000403
|
||||
0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4}
|
||||
byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage, 48000);
|
||||
EventMessage eventMessage =
|
||||
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||
byte[] expectedEmsgBody =
|
||||
new byte[] {
|
||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||
49, 50, 51, 0, // value = "123"
|
||||
0, 0, 3, -24, // timescale = 1000
|
||||
0, 0, 0, 0, // presentation_time_delta = 0
|
||||
0, 0, 11, -72, // event_duration = 3000
|
||||
0, 15, 67, -45, // id = 1000403
|
||||
0, 1, 2, 3, 4
|
||||
}; // message_data = {0, 1, 2, 3, 4}
|
||||
byte[] encodedByteArray = new EventMessageEncoder().encode(eventMessage);
|
||||
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeDecodeEventStream() throws IOException {
|
||||
EventMessage expectedEmsg = new EventMessage("urn:test", "123", 3000, 1000403,
|
||||
new byte[] {0, 1, 2, 3, 4}, 1000000);
|
||||
byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg, 48000);
|
||||
EventMessage expectedEmsg =
|
||||
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||
byte[] encodedByteArray = new EventMessageEncoder().encode(expectedEmsg);
|
||||
MetadataInputBuffer buffer = new MetadataInputBuffer();
|
||||
buffer.data = ByteBuffer.allocate(encodedByteArray.length).put(encodedByteArray);
|
||||
|
||||
|
|
@ -63,30 +65,34 @@ public final class EventMessageEncoderTest {
|
|||
|
||||
@Test
|
||||
public void testEncodeEventStreamMultipleTimesWorkingCorrectly() throws IOException {
|
||||
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
|
||||
new byte[] {0, 1, 2, 3, 4}, 1000000);
|
||||
byte[] expectedEmsgBody = new byte[] {
|
||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||
49, 50, 51, 0, // value = "123"
|
||||
0, 0, -69, -128, // timescale = 48000
|
||||
0, 0, -69, -128, // presentation_time_delta = 48000
|
||||
0, 2, 50, -128, // event_duration = 144000
|
||||
0, 15, 67, -45, // id = 1000403
|
||||
0, 1, 2, 3, 4}; // message_data = {0, 1, 2, 3, 4}
|
||||
EventMessage eventMessage1 = new EventMessage("urn:test", "123", 3000, 1000402,
|
||||
new byte[] {4, 3, 2, 1, 0}, 1000000);
|
||||
byte[] expectedEmsgBody1 = new byte[] {
|
||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||
49, 50, 51, 0, // value = "123"
|
||||
0, 0, -69, -128, // timescale = 48000
|
||||
0, 0, -69, -128, // presentation_time_delta = 48000
|
||||
0, 2, 50, -128, // event_duration = 144000
|
||||
0, 15, 67, -46, // id = 1000402
|
||||
4, 3, 2, 1, 0}; // message_data = {4, 3, 2, 1, 0}
|
||||
EventMessage eventMessage =
|
||||
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||
byte[] expectedEmsgBody =
|
||||
new byte[] {
|
||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||
49, 50, 51, 0, // value = "123"
|
||||
0, 0, 3, -24, // timescale = 1000
|
||||
0, 0, 0, 0, // presentation_time_delta = 0
|
||||
0, 0, 11, -72, // event_duration = 3000
|
||||
0, 15, 67, -45, // id = 1000403
|
||||
0, 1, 2, 3, 4
|
||||
}; // message_data = {0, 1, 2, 3, 4}
|
||||
EventMessage eventMessage1 =
|
||||
new EventMessage("urn:test", "123", 3000, 1000402, new byte[] {4, 3, 2, 1, 0});
|
||||
byte[] expectedEmsgBody1 =
|
||||
new byte[] {
|
||||
117, 114, 110, 58, 116, 101, 115, 116, 0, // scheme_id_uri = "urn:test"
|
||||
49, 50, 51, 0, // value = "123"
|
||||
0, 0, 3, -24, // timescale = 1000
|
||||
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();
|
||||
byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage, 48000);
|
||||
byte[] encodedByteArray = eventMessageEncoder.encode(eventMessage);
|
||||
assertThat(encodedByteArray).isEqualTo(expectedEmsgBody);
|
||||
byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1, 48000);
|
||||
byte[] encodedByteArray1 = eventMessageEncoder.encode(eventMessage1);
|
||||
assertThat(encodedByteArray1).isEqualTo(expectedEmsgBody1);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ public final class EventMessageTest {
|
|||
|
||||
@Test
|
||||
public void testEventMessageParcelable() {
|
||||
EventMessage eventMessage = new EventMessage("urn:test", "123", 3000, 1000403,
|
||||
new byte[] {0, 1, 2, 3, 4}, 1000);
|
||||
EventMessage eventMessage =
|
||||
new EventMessage("urn:test", "123", 3000, 1000403, new byte[] {0, 1, 2, 3, 4});
|
||||
// Write to parcel.
|
||||
Parcel parcel = Parcel.obtain();
|
||||
eventMessage.writeToParcel(parcel, 0);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import android.support.annotation.Nullable;
|
|||
import android.text.TextUtils;
|
||||
import android.util.SparseArray;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
|
|
@ -614,10 +613,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
this.mediaTransferListener = mediaTransferListener;
|
||||
if (sideloadedManifest) {
|
||||
processManifest(false);
|
||||
|
|
|
|||
|
|
@ -112,8 +112,7 @@ import java.io.IOException;
|
|||
}
|
||||
}
|
||||
int sampleIndex = currentIndex++;
|
||||
byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex],
|
||||
eventStream.timescale);
|
||||
byte[] serializedEvent = eventMessageEncoder.encode(eventStream.events[sampleIndex]);
|
||||
if (serializedEvent != null) {
|
||||
buffer.ensureSpaceForWrite(serializedEvent.length);
|
||||
buffer.setFlags(C.BUFFER_FLAG_KEY_FRAME);
|
||||
|
|
|
|||
|
|
@ -834,13 +834,13 @@ public class DashManifestParser extends DefaultHandler
|
|||
String schemeIdUri = parseString(xpp, "schemeIdUri", "");
|
||||
String value = parseString(xpp, "value", "");
|
||||
long timescale = parseLong(xpp, "timescale", 1);
|
||||
List<EventMessage> eventMessages = new ArrayList<>();
|
||||
List<Pair<Long, EventMessage>> eventMessages = new ArrayList<>();
|
||||
ByteArrayOutputStream scratchOutputStream = new ByteArrayOutputStream(512);
|
||||
do {
|
||||
xpp.next();
|
||||
if (XmlPullParserUtil.isStartTag(xpp, "Event")) {
|
||||
EventMessage event = parseEvent(xpp, schemeIdUri, value, timescale,
|
||||
scratchOutputStream);
|
||||
Pair<Long, EventMessage> event =
|
||||
parseEvent(xpp, schemeIdUri, value, timescale, scratchOutputStream);
|
||||
eventMessages.add(event);
|
||||
} else {
|
||||
maybeSkipTag(xpp);
|
||||
|
|
@ -850,9 +850,9 @@ public class DashManifestParser extends DefaultHandler
|
|||
long[] presentationTimesUs = new long[eventMessages.size()];
|
||||
EventMessage[] events = new EventMessage[eventMessages.size()];
|
||||
for (int i = 0; i < eventMessages.size(); i++) {
|
||||
EventMessage event = eventMessages.get(i);
|
||||
presentationTimesUs[i] = event.presentationTimeUs;
|
||||
events[i] = event;
|
||||
Pair<Long, EventMessage> event = eventMessages.get(i);
|
||||
presentationTimesUs[i] = event.first;
|
||||
events[i] = event.second;
|
||||
}
|
||||
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 scratchOutputStream A {@link ByteArrayOutputStream} that is used when parsing event
|
||||
* 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 IOException If there is any error reading from the underlying input stream.
|
||||
*/
|
||||
protected EventMessage parseEvent(
|
||||
protected Pair<Long, EventMessage> parseEvent(
|
||||
XmlPullParser xpp,
|
||||
String schemeIdUri,
|
||||
String value,
|
||||
|
|
@ -890,13 +891,14 @@ public class DashManifestParser extends DefaultHandler
|
|||
timescale);
|
||||
String messageData = parseString(xpp, "messageData", null);
|
||||
byte[] eventObject = parseEventObject(xpp, scratchOutputStream);
|
||||
return buildEvent(
|
||||
schemeIdUri,
|
||||
value,
|
||||
id,
|
||||
durationMs,
|
||||
messageData == null ? eventObject : Util.getUtf8Bytes(messageData),
|
||||
presentationTimesUs);
|
||||
return Pair.create(
|
||||
presentationTimesUs,
|
||||
buildEvent(
|
||||
schemeIdUri,
|
||||
value,
|
||||
id,
|
||||
durationMs,
|
||||
messageData == null ? eventObject : Util.getUtf8Bytes(messageData)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -963,9 +965,9 @@ public class DashManifestParser extends DefaultHandler
|
|||
return scratchOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
protected EventMessage buildEvent(String schemeIdUri, String value, long id,
|
||||
long durationMs, byte[] messageData, long presentationTimeUs) {
|
||||
return new EventMessage(schemeIdUri, value, durationMs, id, messageData, presentationTimeUs);
|
||||
protected EventMessage buildEvent(
|
||||
String schemeIdUri, String value, long id, long durationMs, byte[] messageData) {
|
||||
return new EventMessage(schemeIdUri, value, durationMs, id, messageData);
|
||||
}
|
||||
|
||||
protected List<SegmentTimelineElement> parseSegmentTimeline(XmlPullParser xpp)
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ public final class EventSampleStreamTest {
|
|||
@Test
|
||||
public void testReadDataReturnDataAfterFormat() {
|
||||
long presentationTimeUs = 1000000;
|
||||
EventMessage eventMessage = newEventMessageWithIdAndTime(1, presentationTimeUs);
|
||||
EventMessage eventMessage = newEventMessageWithId(1);
|
||||
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs}, new EventMessage[] {eventMessage});
|
||||
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
|
||||
|
|
@ -133,8 +133,8 @@ public final class EventSampleStreamTest {
|
|||
public void testSkipDataThenReadDataReturnDataFromSkippedPosition() {
|
||||
long presentationTimeUs1 = 1000000;
|
||||
long presentationTimeUs2 = 2000000;
|
||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
||||
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||
new EventMessage[] {eventMessage1, eventMessage2});
|
||||
|
|
@ -159,8 +159,8 @@ public final class EventSampleStreamTest {
|
|||
public void testSeekToUsThenReadDataReturnDataFromSeekPosition() {
|
||||
long presentationTimeUs1 = 1000000;
|
||||
long presentationTimeUs2 = 2000000;
|
||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
||||
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||
new EventMessage[] {eventMessage1, eventMessage2});
|
||||
|
|
@ -186,9 +186,9 @@ public final class EventSampleStreamTest {
|
|||
long presentationTimeUs1 = 1000000;
|
||||
long presentationTimeUs2 = 2000000;
|
||||
long presentationTimeUs3 = 3000000;
|
||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
||||
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||
new EventMessage[] {eventMessage1, eventMessage2});
|
||||
|
|
@ -220,9 +220,9 @@ public final class EventSampleStreamTest {
|
|||
long presentationTimeUs1 = 1000000;
|
||||
long presentationTimeUs2 = 2000000;
|
||||
long presentationTimeUs3 = 3000000;
|
||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
||||
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||
new EventMessage[] {eventMessage1, eventMessage2});
|
||||
|
|
@ -253,9 +253,9 @@ public final class EventSampleStreamTest {
|
|||
long presentationTimeUs1 = 1000000;
|
||||
long presentationTimeUs2 = 2000000;
|
||||
long presentationTimeUs3 = 3000000;
|
||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
||||
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs1},
|
||||
new EventMessage[] {eventMessage1});
|
||||
|
|
@ -287,9 +287,9 @@ public final class EventSampleStreamTest {
|
|||
long presentationTimeUs1 = 1000000;
|
||||
long presentationTimeUs2 = 2000000;
|
||||
long presentationTimeUs3 = 3000000;
|
||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
||||
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs1, presentationTimeUs2},
|
||||
new EventMessage[] {eventMessage1, eventMessage2});
|
||||
|
|
@ -319,9 +319,9 @@ public final class EventSampleStreamTest {
|
|||
long presentationTimeUs1 = 1000000;
|
||||
long presentationTimeUs2 = 2000000;
|
||||
long presentationTimeUs3 = 3000000;
|
||||
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
|
||||
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
|
||||
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
|
||||
EventMessage eventMessage1 = newEventMessageWithId(1);
|
||||
EventMessage eventMessage2 = newEventMessageWithId(2);
|
||||
EventMessage eventMessage3 = newEventMessageWithId(3);
|
||||
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
|
||||
new long[] {presentationTimeUs1},
|
||||
new EventMessage[] {eventMessage1});
|
||||
|
|
@ -345,12 +345,12 @@ public final class EventSampleStreamTest {
|
|||
return sampleStream.readData(formatHolder, inputBuffer, false);
|
||||
}
|
||||
|
||||
private EventMessage newEventMessageWithIdAndTime(int id, long presentationTimeUs) {
|
||||
return new EventMessage(SCHEME_ID, VALUE, DURATION_MS, id, MESSAGE_DATA, presentationTimeUs);
|
||||
private EventMessage newEventMessageWithId(int id) {
|
||||
return new EventMessage(SCHEME_ID, VALUE, DURATION_MS, id, MESSAGE_DATA);
|
||||
}
|
||||
|
||||
private byte[] getEncodedMessage(EventMessage eventMessage) {
|
||||
return eventMessageEncoder.encode(eventMessage, TIME_SCALE);
|
||||
return eventMessageEncoder.encode(eventMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,52 +104,53 @@ public class DashManifestParserTest {
|
|||
"call",
|
||||
10000,
|
||||
0,
|
||||
"+ 1 800 10101010".getBytes(Charset.forName(C.UTF8_NAME)),
|
||||
0);
|
||||
"+ 1 800 10101010".getBytes(Charset.forName(C.UTF8_NAME)));
|
||||
assertThat(eventStream1.events[0]).isEqualTo(expectedEvent1);
|
||||
assertThat(eventStream1.presentationTimesUs[0]).isEqualTo(0);
|
||||
|
||||
// assert CData-structured event stream
|
||||
EventStream eventStream2 = period.eventStreams.get(1);
|
||||
assertThat(eventStream2.events.length).isEqualTo(1);
|
||||
assertThat(eventStream2.events[0])
|
||||
.isEqualTo(
|
||||
new EventMessage(
|
||||
"urn:dvb:iptv:cpm:2014",
|
||||
"",
|
||||
1500000,
|
||||
1,
|
||||
Util.getUtf8Bytes(
|
||||
"<![CDATA[<BroadcastEvent>\n"
|
||||
+ " <Program crid=\"crid://broadcaster.example.com/ABCDEF\"/>\n"
|
||||
+ " <InstanceDescription>\n"
|
||||
+ " <Title xml:lang=\"en\">The title</Title>\n"
|
||||
+ " <Synopsis xml:lang=\"en\" length=\"medium\">"
|
||||
+ "The description</Synopsis>\n"
|
||||
+ " <ParentalGuidance>\n"
|
||||
+ " <mpeg7:ParentalRating href=\"urn:dvb:iptv:rating:2014:15\"/>\n"
|
||||
+ " <mpeg7:Region>GB</mpeg7:Region>\n"
|
||||
+ " </ParentalGuidance>\n"
|
||||
+ " </InstanceDescription>\n"
|
||||
+ " </BroadcastEvent>]]>"),
|
||||
300000000));
|
||||
EventMessage expectedEvent2 =
|
||||
new EventMessage(
|
||||
"urn:dvb:iptv:cpm:2014",
|
||||
"",
|
||||
1500000,
|
||||
1,
|
||||
Util.getUtf8Bytes(
|
||||
"<![CDATA[<BroadcastEvent>\n"
|
||||
+ " <Program crid=\"crid://broadcaster.example.com/ABCDEF\"/>\n"
|
||||
+ " <InstanceDescription>\n"
|
||||
+ " <Title xml:lang=\"en\">The title</Title>\n"
|
||||
+ " <Synopsis xml:lang=\"en\" length=\"medium\">"
|
||||
+ "The description</Synopsis>\n"
|
||||
+ " <ParentalGuidance>\n"
|
||||
+ " <mpeg7:ParentalRating href=\"urn:dvb:iptv:rating:2014:15\"/>\n"
|
||||
+ " <mpeg7:Region>GB</mpeg7:Region>\n"
|
||||
+ " </ParentalGuidance>\n"
|
||||
+ " </InstanceDescription>\n"
|
||||
+ " </BroadcastEvent>]]>"));
|
||||
|
||||
assertThat(eventStream2.events[0]).isEqualTo(expectedEvent2);
|
||||
assertThat(eventStream2.presentationTimesUs[0]).isEqualTo(300000000);
|
||||
|
||||
// assert xml-structured event stream
|
||||
EventStream eventStream3 = period.eventStreams.get(2);
|
||||
assertThat(eventStream3.events.length).isEqualTo(1);
|
||||
assertThat(eventStream3.events[0])
|
||||
.isEqualTo(
|
||||
new EventMessage(
|
||||
"urn:scte:scte35:2014:xml+bin",
|
||||
"",
|
||||
1000000,
|
||||
2,
|
||||
Util.getUtf8Bytes(
|
||||
"<scte35:Signal>\n"
|
||||
+ " <scte35:Binary>\n"
|
||||
+ " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n"
|
||||
+ " </scte35:Binary>\n"
|
||||
+ " </scte35:Signal>"),
|
||||
1000000000));
|
||||
EventMessage expectedEvent3 =
|
||||
new EventMessage(
|
||||
"urn:scte:scte35:2014:xml+bin",
|
||||
"",
|
||||
1000000,
|
||||
2,
|
||||
Util.getUtf8Bytes(
|
||||
"<scte35:Signal>\n"
|
||||
+ " <scte35:Binary>\n"
|
||||
+ " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n"
|
||||
+ " </scte35:Binary>\n"
|
||||
+ " </scte35:Signal>"));
|
||||
assertThat(eventStream3.events[0]).isEqualTo(expectedEvent3);
|
||||
assertThat(eventStream3.presentationTimesUs[0]).isEqualTo(1000000000);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import android.net.Uri;
|
|||
import android.os.Handler;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
|
|
@ -397,10 +396,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
this.mediaTransferListener = mediaTransferListener;
|
||||
EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
|
||||
playlistTracker.start(manifestUri, eventDispatcher, /* listener= */ this);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ import java.io.IOException;
|
|||
|
||||
@Override
|
||||
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()
|
||||
? sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, requireFormat)
|
||||
: C.RESULT_NOTHING_READ;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import android.os.Handler;
|
|||
import android.os.SystemClock;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import com.google.android.exoplayer2.ParserException;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
|
|
@ -510,10 +509,7 @@ public final class SsMediaSource extends BaseMediaSource
|
|||
}
|
||||
|
||||
@Override
|
||||
public void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
this.mediaTransferListener = mediaTransferListener;
|
||||
if (sideloadedManifest) {
|
||||
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.id3.ApicFrame;
|
||||
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.TextOutput;
|
||||
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.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
|
@ -221,6 +223,11 @@ import java.util.List;
|
|||
* <ul>
|
||||
* <li>Type: {@link PlayerControlView}
|
||||
* </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
|
||||
* the app can access via {@link #getOverlayFrameLayout()}, provided for convenience.
|
||||
* <ul>
|
||||
|
|
@ -239,7 +246,7 @@ import java.util.List;
|
|||
* 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.
|
||||
*/
|
||||
public class PlayerView extends FrameLayout {
|
||||
public class PlayerView extends FrameLayout implements AdsLoader.AdViewProvider {
|
||||
|
||||
// LINT.IfChange
|
||||
/**
|
||||
|
|
@ -278,9 +285,10 @@ public class PlayerView extends FrameLayout {
|
|||
private final SubtitleView subtitleView;
|
||||
@Nullable private final View bufferingView;
|
||||
@Nullable private final TextView errorMessageView;
|
||||
private final PlayerControlView controller;
|
||||
@Nullable private final PlayerControlView controller;
|
||||
private final ComponentListener componentListener;
|
||||
private final FrameLayout overlayFrameLayout;
|
||||
@Nullable private final FrameLayout adOverlayFrameLayout;
|
||||
@Nullable private final FrameLayout overlayFrameLayout;
|
||||
|
||||
private Player player;
|
||||
private boolean useController;
|
||||
|
|
@ -317,6 +325,7 @@ public class PlayerView extends FrameLayout {
|
|||
errorMessageView = null;
|
||||
controller = null;
|
||||
componentListener = null;
|
||||
adOverlayFrameLayout = null;
|
||||
overlayFrameLayout = null;
|
||||
ImageView logo = new ImageView(context);
|
||||
if (Util.SDK_INT >= 23) {
|
||||
|
|
@ -411,6 +420,9 @@ public class PlayerView extends FrameLayout {
|
|||
surfaceView = null;
|
||||
}
|
||||
|
||||
// Ad overlay frame layout.
|
||||
adOverlayFrameLayout = findViewById(R.id.exo_ad_overlay);
|
||||
|
||||
// Overlay frame layout.
|
||||
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
|
||||
* the overlay is not present.
|
||||
*/
|
||||
@Nullable
|
||||
public FrameLayout getOverlayFrameLayout() {
|
||||
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() {
|
||||
if (!useController || player == null) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@
|
|||
|
||||
</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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
<item name="exo_artwork" type="id"/>
|
||||
<item name="exo_controller_placeholder" type="id"/>
|
||||
<item name="exo_controller" type="id"/>
|
||||
<item name="exo_ad_overlay" type="id"/>
|
||||
<item name="exo_overlay" type="id"/>
|
||||
<item name="exo_play" type="id"/>
|
||||
<item name="exo_pause" type="id"/>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import android.os.Handler;
|
|||
import android.os.SystemClock;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Timeline.Period;
|
||||
|
|
@ -96,10 +95,7 @@ public class FakeMediaSource extends BaseMediaSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public synchronized void prepareSourceInternal(
|
||||
ExoPlayer player,
|
||||
boolean isTopLevelSource,
|
||||
@Nullable TransferListener mediaTransferListener) {
|
||||
public synchronized void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
assertThat(preparedSource).isFalse();
|
||||
transferListener = mediaTransferListener;
|
||||
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.assertWithMessage;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.support.annotation.Nullable;
|
||||
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.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
|
|
@ -54,7 +50,6 @@ public class MediaSourceTestRunner {
|
|||
|
||||
public static final int TIMEOUT_MS = 10000;
|
||||
|
||||
private final StubExoPlayer player;
|
||||
private final MediaSource mediaSource;
|
||||
private final MediaSourceListener mediaSourceListener;
|
||||
private final HandlerThread playbackThread;
|
||||
|
|
@ -79,7 +74,6 @@ public class MediaSourceTestRunner {
|
|||
playbackThread.start();
|
||||
Looper playbackLooper = playbackThread.getLooper();
|
||||
playbackHandler = new Handler(playbackLooper);
|
||||
player = new EventHandlingExoPlayer(playbackLooper);
|
||||
mediaSourceListener = new MediaSourceListener();
|
||||
timelines = new LinkedBlockingDeque<>();
|
||||
completedLoads = new CopyOnWriteArrayList<>();
|
||||
|
|
@ -121,11 +115,7 @@ public class MediaSourceTestRunner {
|
|||
final IOException[] prepareError = new IOException[1];
|
||||
runOnPlaybackThread(
|
||||
() -> {
|
||||
mediaSource.prepareSource(
|
||||
player,
|
||||
/* isTopLevelSource= */ true,
|
||||
mediaSourceListener,
|
||||
/* mediaTransferListener= */ null);
|
||||
mediaSource.prepareSource(mediaSourceListener, /* mediaTransferListener= */ null);
|
||||
try {
|
||||
// TODO: This only catches errors that are set synchronously in prepareSource. To
|
||||
// capture async errors we'll need to poll maybeThrowSourceInfoRefreshError until the
|
||||
|
|
@ -430,43 +420,4 @@ public class MediaSourceTestRunner {
|
|||
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