diff --git a/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE/bug.md similarity index 53% rename from ISSUE_TEMPLATE rename to .github/ISSUE_TEMPLATE/bug.md index 8d2f66093d..7e9c631fc0 100644 --- a/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,13 +1,20 @@ -Before filing an issue: +--- +name: Bug report +about: Issue template for a bug report. +title: '' +labels: bug, 'needs triage' +assignees: '' +--- + +Before filing a bug: ----------------------- - Search existing issues, including issues that are closed. - Consult our FAQs, supported devices and supported formats pages. These can be found at https://google.github.io/ExoPlayer/. - Rule out issues in your own code. A good way to do this is to try and - reproduce the issue in the ExoPlayer demo app. -- This issue tracker is intended for bugs, feature requests and ExoPlayer - specific questions. If you're asking a general Android development question, - please do so on Stack Overflow. + reproduce the issue in the ExoPlayer demo app. Information about the ExoPlayer + demo app can be found here: + http://google.github.io/ExoPlayer/demo-application.html. When reporting a bug: ----------------------- @@ -15,29 +22,33 @@ Fill out the sections below, leaving the headers but replacing the content. If you're unable to provide certain information, please explain why in the relevant section. We may close issues if they do not include sufficient information. -### Issue description +### [REQUIRED] Issue description Describe the issue in detail, including observed and expected behavior. -### Reproduction steps -Describe how the issue can be reproduced, ideally using the ExoPlayer demo app. +### [REQUIRED] Reproduction steps +Describe how the issue can be reproduced, ideally using the ExoPlayer demo app +or a small sample app that you’re able to share as source code on GitHub. -### Link to test content -Provide a link to media that reproduces the issue. If you don't wish to post it -publicly, please submit the issue, then email the link to -dev.exoplayer@gmail.com using a subject in the format "Issue #1234". +### [REQUIRED] Link to test content +Provide a JSON snippet for the demo app’s media.exolist.json file, or a link to +media that reproduces the issue. If you don't wish to post it publicly, please +submit the issue, then email the link to dev.exoplayer@gmail.com using a subject +in the format "Issue #1234". Provide all the metadata we'd need to play the +content like drm license urls or similar. If the content is accessible only in +certain countries or regions, please say so. -### Version of ExoPlayer being used -Specify the absolute version number. Avoid using terms such as "latest". - -### Device(s) and version(s) of Android being used -Specify the devices and versions of Android on which the issue can be -reproduced, and how easily it reproduces. If possible, please test on multiple -devices and Android versions. - -### A full bug report captured from the device +### [REQUIRED] A full bug report captured from the device Capture a full bug report using "adb bugreport". Output from "adb logcat" or a log snippet is NOT sufficient. Please attach the captured bug report as a file. If you don't wish to post it publicly, please submit the issue, then email the bug report to dev.exoplayer@gmail.com using a subject in the format "Issue #1234". +### [REQUIRED] Version of ExoPlayer being used +Specify the absolute version number. Avoid using terms such as "latest". + +### [REQUIRED] Device(s) and version(s) of Android being used +Specify the devices and versions of Android on which the issue can be +reproduced, and how easily it reproduces. If possible, please test on multiple +devices and Android versions. + diff --git a/.github/ISSUE_TEMPLATE/content_not_playing.md b/.github/ISSUE_TEMPLATE/content_not_playing.md new file mode 100644 index 0000000000..47c09a7a59 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/content_not_playing.md @@ -0,0 +1,44 @@ +--- +name: Content not playing correctly +about: Issue template for a content not playing issue. +title: '' +labels: 'content not playing', 'needs triage' +assignees: '' +--- + +Before filing a content issue: +------------------------------ +- Search existing issues, including issues that are closed. +- Consult our supported formats page, which can be found at + https://google.github.io/ExoPlayer/supported-formats.html. +- Try playing your content in the ExoPlayer demo app. Information about the + ExoPlayer demo app can be found here: + http://google.github.io/ExoPlayer/demo-application.html. + +When reporting a content issue: +----------------------------- +Fill out the sections below, leaving the headers but replacing the content. If +you're unable to provide certain information, please explain why in the relevant +section. We may close issues if they do not include sufficient information. + +### [REQUIRED] Content description +Describe the content and any specifics you expected to play but did not. This +could be the container or sample format itself or any features the stream has +and you expect to play, like 5.1 audio track, text tracks or drm systems. + +### [REQUIRED] Link to test content +Provide a JSON snippet for the demo app’s media.exolist.json file, or a link to +media that reproduces the issue. If you don't wish to post it publicly, please +submit the issue, then email the link to dev.exoplayer@gmail.com using a subject +in the format "Issue #1234". Provide all the metadata we'd need to play the +content like drm license urls or similar. If the content is accessible only in +certain countries or regions, please say so. + +### [REQUIRED] Version of ExoPlayer being used +Specify the absolute version number. Avoid using terms such as "latest". + +### [REQUIRED] Device(s) and version(s) of Android being used +Specify the devices and versions of Android on which you expect the content to +play. If possible, please test on multiple devices and Android versions. + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..17a07ef39b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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. + + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000000..9d0ddfe4a1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,31 @@ +--- +name: Question +about: Issue template for a question. +title: '' +labels: question, 'needs triage' +assignees: '' +--- + +Before filing a question: +----------------------- +- This issue tracker is intended ExoPlayer specific questions. If you're asking + a general Android development question, please do so on Stack Overflow. +- Search existing issues, including issues that are closed. It’s often the + quickest way to get an answer! +- Consult our FAQs, developer guide and the class reference of ExoPlayer. These + can be found at https://google.github.io/ExoPlayer/. + +When filing a question: +----------------------- +Fill out the sections below, leaving the headers but replacing the content. If +you're unable to provide certain information, please explain why in the relevant +section. We may close issues if they do not include sufficient information. + +### [REQUIRED] Searched documentation and issues +Tell us where you’ve already looked for an answer to your question. It’s +important for us to know this so that we can improve our documentation. + +### [REQUIRED] Question +Describe your question in detail. + + diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b727e4ab49..6c87dd02b4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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. diff --git a/constants.gradle b/constants.gradle index 16fb3420ce..d7349c3c66 100644 --- a/constants.gradle +++ b/constants.gradle @@ -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 diff --git a/demos/ima/src/main/AndroidManifest.xml b/demos/ima/src/main/AndroidManifest.xml index 50ad0c1b54..85439018fd 100644 --- a/demos/ima/src/main/AndroidManifest.xml +++ b/demos/ima/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ package="com.google.android.exoplayer2.imademo"> + 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()}. + * + *

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 adUiElements; + @Nullable private ImaSdkSettings imaSdkSettings; + @Nullable private AdEventListener adEventListener; + @Nullable private Set 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 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. + * + *

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 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 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); } } } diff --git a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java b/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java deleted file mode 100644 index 0978ee401c..0000000000 --- a/extensions/ima/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsMediaSource.java +++ /dev/null @@ -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); - } -} diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java index b8024d6534..d20ccbd728 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/FakePlayer.java @@ -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; diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java index b0fe731480..dabae2de4b 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoaderTest.java @@ -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) { diff --git a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/SingletonImaFactory.java b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/SingletonImaFactory.java index dd46d8a68b..4efd8cf38c 100644 --- a/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/SingletonImaFactory.java +++ b/extensions/ima/src/test/java/com/google/android/exoplayer2/ext/ima/SingletonImaFactory.java @@ -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; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 35fa85e467..de6e867514 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -138,7 +138,6 @@ import java.util.concurrent.CopyOnWriteArraySet; repeatMode, shuffleModeEnabled, eventHandler, - this, clock); internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper()); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 7f41719d1d..c31c6b75a5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -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); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index e3a2e1cd27..b389ec742f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -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} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 0f1fd8f649..d7ed1c0c5b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -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); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java index 7d70d9de1c..8a3467e2ed 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessage.java @@ -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. - *

- * Except in special cases, application code should not 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); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java index 14f678374c..33d79917eb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoder.java @@ -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. - *

- * 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). + * + *

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)); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java index eca498a6df..22708a8448 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoder.java @@ -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(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java index 2feac2978e..189467b47e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/BaseMediaSource.java @@ -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 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(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java index fce1c4b877..d5399dc02d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ClippingMediaSource.java @@ -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 { } @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); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java index 69fa4b094b..dbf5812f98 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/CompositeMediaSource.java @@ -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 extends BaseMediaSource { private final HashMap childSources; - private @Nullable ExoPlayer player; private @Nullable Handler eventHandler; private @Nullable TransferListener mediaTransferListener; @@ -46,11 +44,7 @@ public abstract class CompositeMediaSource 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 extends BaseMediaSource { childSource.mediaSource.removeEventListener(childSource.eventListener); } childSources.clear(); - player = null; } /** @@ -105,11 +98,7 @@ public abstract class CompositeMediaSource 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); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java index 7baea9979f..961aaf105f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java @@ -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 { } @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); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java index 801737faef..14f9a26245 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MediaSource.java @@ -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: * *

    *
  • 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)}. *
  • 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. *
* * 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 { *

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. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java index cc7202f9b2..1ea3404e81 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/MergingMediaSource.java @@ -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 { } @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]); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java index 046672bb77..c028a9e339 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/SingleSampleMediaSource.java @@ -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); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java index f041542356..b3054f69a0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsLoader.java @@ -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}. * - *

Ad loaders notify the {@link AdsMediaSource} about events via {@link EventListener}. In + *

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). * - *

{@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)}. + *

{@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)}. * *

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). + * + *

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. + * + *

This method must be called before the player is prepared with media using this ads loader. + * + *

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. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java index 4bf661ddc0..c57ad6a223 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/ads/AdsMediaSource.java @@ -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 { } } - /** - * 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 { 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> deferredMediaPeriodByAdMediaSource; private final Timeline.Period period; @@ -209,20 +165,18 @@ public final class AdsMediaSource extends CompositeMediaSource { * @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 { * @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 { } @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 { 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 { } 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 { }); } - @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 { /* 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); - } - } - }); - } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 5b35ee946a..3e262a567d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -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. - * - *

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: * diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index d131ed0f51..fd9100338c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -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. diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java index c6558e3fc9..85d336b439 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageDecoderTest.java @@ -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); } } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java index 7195548fbf..2869692272 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageEncoderTest.java @@ -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); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java index 30e1cd6c1f..f7970d1a16 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/metadata/emsg/EventMessageTest.java @@ -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); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java index c65bfceb39..8b503989b7 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaSource.java @@ -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); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java index 9f812b8e84..f06a709960 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/EventSampleStream.java @@ -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); diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index f017ae64ad..f34127273d 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -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 eventMessages = new ArrayList<>(); + List> 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 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 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 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 parseSegmentTimeline(XmlPullParser xpp) diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java index 9c3752551a..9621381eed 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/EventSampleStreamTest.java @@ -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); } } diff --git a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index a1693f6985..67d8dbecac 100644 --- a/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/dash/src/test/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -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( - "\n" - + " \n" - + " \n" - + " The title\n" - + " " - + "The description\n" - + " \n" - + " \n" - + " GB\n" - + " \n" - + " \n" - + " ]]>"), - 300000000)); + EventMessage expectedEvent2 = + new EventMessage( + "urn:dvb:iptv:cpm:2014", + "", + 1500000, + 1, + Util.getUtf8Bytes( + "\n" + + " \n" + + " \n" + + " The title\n" + + " " + + "The description\n" + + " \n" + + " \n" + + " GB\n" + + " \n" + + " \n" + + " ]]>")); + + 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( - "\n" - + " \n" - + " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n" - + " \n" - + " "), - 1000000000)); + EventMessage expectedEvent3 = + new EventMessage( + "urn:scte:scte35:2014:xml+bin", + "", + 1000000, + 2, + Util.getUtf8Bytes( + "\n" + + " \n" + + " /DAIAAAAAAAAAAAQAAZ/I0VniQAQAgBDVUVJQAAAAH+cAAAAAA==\n" + + " \n" + + " ")); + assertThat(eventStream3.events[0]).isEqualTo(expectedEvent3); + assertThat(eventStream3.presentationTimesUs[0]).isEqualTo(1000000000); } @Test diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java index 2afd041631..cd2cbbcab9 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java @@ -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); diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java index f43d119018..cf879e91c6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java @@ -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; diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index d025f8fa3a..fb64f43772 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -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(); diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java index 9742d0005a..9d66289e94 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerView.java @@ -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; *

    *
  • Type: {@link PlayerControlView} *
+ *
  • {@code exo_ad_overlay} - A {@link FrameLayout} positioned on top of the player which + * is used to show ad UI (if applicable). + *
      + *
    • Type: {@link FrameLayout} + *
    *
  • {@code exo_overlay} - A {@link FrameLayout} positioned on top of the player which * the app can access via {@link #getOverlayFrameLayout()}, provided for convenience. *
      @@ -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 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; diff --git a/library/ui/src/main/res/layout/exo_simple_player_view.xml b/library/ui/src/main/res/layout/exo_simple_player_view.xml index 167ac96222..65dea9271e 100644 --- a/library/ui/src/main/res/layout/exo_simple_player_view.xml +++ b/library/ui/src/main/res/layout/exo_simple_player_view.xml @@ -52,6 +52,10 @@ + + diff --git a/library/ui/src/main/res/values/ids.xml b/library/ui/src/main/res/values/ids.xml index 184e51ac58..6f798adcb4 100644 --- a/library/ui/src/main/res/values/ids.xml +++ b/library/ui/src/main/res/values/ids.xml @@ -21,6 +21,7 @@ + diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java index 999372b90a..de4be82b38 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeMediaSource.java @@ -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; diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java index e6fb5bc5f3..9514768416 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/MediaSourceTestRunner.java @@ -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; - } - } }