Merge pull request #5553 from google/dev-v2-r2.9.6

r2.9.6
This commit is contained in:
Andrew Lewis 2019-02-21 15:03:06 +00:00 committed by GitHub
commit 8ec4aad001
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 638 additions and 773 deletions

View file

@ -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 youre 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 apps 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.

View 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 apps 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.

View 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
View 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. Its 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 youve already looked for an answer to your question. Its
important for us to know this so that we can improve our documentation.
### [REQUIRED] Question
Describe your question in detail.

View file

@ -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.

View file

@ -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

View file

@ -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"

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -138,7 +138,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
repeatMode,
shuffleModeEnabled,
eventHandler,
this,
clock);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
}

View file

@ -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);
}

View file

@ -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}

View file

@ -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);

View file

@ -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);

View file

@ -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));
}
}

View file

@ -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();

View file

@ -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();

View file

@ -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);
}

View file

@ -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);
}
/**

View file

@ -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.
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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.

View file

@ -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]);
}

View file

@ -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);
}

View file

@ -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.

View file

@ -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);
}
}
});
}
}
}

View file

@ -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:
*

View file

@ -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.

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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)

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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"/>

View file

@ -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"/>

View file

@ -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;

View file

@ -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;
}
}
}