Commit 8ec4aad0 by Andrew Lewis Committed by GitHub

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

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