Commit 1e2345cf by tonihei Committed by Ian Baker

Simplify StreamRequest.Builder to an Uri Builder and make it public

Right now, the option to build an IMA DAI URI programmatically is still
package-private. To simplify the process, we can remove the StreamRequest
wrapper and directly provide an URI builder.

The same class can provide some package-private helper methods to parse the
created URI.

#minor-release

PiperOrigin-RevId: 427445326
parent d084a712
......@@ -321,7 +321,10 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
private final com.google.ads.interactivemedia.v3.api.AdsLoader adsLoader;
@Nullable private final AdEventListener applicationAdEventListener;
@Nullable private final AdErrorListener applicationAdErrorListener;
private final ServerSideAdInsertionStreamRequest streamRequest;
private final boolean isLiveStream;
private final String adsId;
private final StreamRequest streamRequest;
private final int loadVideoTimeoutMs;
private final StreamPlayer streamPlayer;
private final Handler mainHandler;
private final ComponentListener componentListener;
......@@ -352,7 +355,10 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
adPlaybackState = AdPlaybackState.NONE;
mainHandler = Util.createHandlerForCurrentLooper();
Uri streamRequestUri = checkNotNull(mediaItem.localConfiguration).uri;
streamRequest = ServerSideAdInsertionStreamRequest.fromUri(streamRequestUri);
isLiveStream = ImaServerSideAdInsertionUriBuilder.isLiveStream(streamRequestUri);
adsId = ImaServerSideAdInsertionUriBuilder.getAdsId(streamRequestUri);
loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(streamRequestUri);
streamRequest = ImaServerSideAdInsertionUriBuilder.createStreamRequest(streamRequestUri);
}
@Override
......@@ -369,10 +375,10 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
StreamManagerLoadable streamManagerLoadable =
new StreamManagerLoadable(
adsLoader,
streamRequest.getStreamRequest(),
streamRequest,
streamPlayer,
applicationAdErrorListener,
streamRequest.loadVideoTimeoutMs);
loadVideoTimeoutMs);
loader.startLoading(
streamManagerLoadable,
new StreamManagerLoadableCallback(),
......@@ -481,7 +487,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
if (!adPlaybackState.equals(AdPlaybackState.NONE) && contentTimeline != null) {
ImmutableMap<Object, AdPlaybackState> splitAdPlaybackStates =
splitAdPlaybackStateForPeriods(adPlaybackState, contentTimeline);
streamPlayer.setAdPlaybackStates(streamRequest.adsId, splitAdPlaybackStates, contentTimeline);
streamPlayer.setAdPlaybackStates(adsId, splitAdPlaybackStates, contentTimeline);
checkNotNull(serverSideAdInsertionMediaSource).setAdPlaybackStates(splitAdPlaybackStates);
}
}
......@@ -497,9 +503,9 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
contentMediaSourceFactory.createMediaSource(MediaItem.fromUri(contentUri)),
componentListener);
this.serverSideAdInsertionMediaSource = serverSideAdInsertionMediaSource;
if (streamRequest.isLiveStream()) {
if (isLiveStream) {
AdPlaybackState liveAdPlaybackState =
new AdPlaybackState(streamRequest.adsId)
new AdPlaybackState(adsId)
.withNewAdGroup(/* adGroupIndex= */ 0, /* adGroupTimeUs= */ C.TIME_END_OF_SOURCE)
.withIsServerSideInserted(/* adGroupIndex= */ 0, true);
mainHandler.post(() -> setAdPlaybackState(liveAdPlaybackState));
......@@ -612,7 +618,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
if (!mediaItem.equals(oldPosition.mediaItem)
|| !mediaItem.equals(newPosition.mediaItem)
|| !streamRequest.adsId.equals(
|| !adsId.equals(
player
.getCurrentTimeline()
.getPeriodByUid(checkNotNull(newPosition.periodUid), new Timeline.Period())
......@@ -646,7 +652,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
@Override
public void onMetadata(Metadata metadata) {
if (!isCurrentAdPlaying(player, mediaItem, streamRequest.adsId)) {
if (!isCurrentAdPlaying(player, mediaItem, adsId)) {
return;
}
for (int i = 0; i < metadata.length(); i++) {
......@@ -666,15 +672,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
@Override
public void onPlaybackStateChanged(@Player.State int state) {
if (state == Player.STATE_ENDED
&& isCurrentAdPlaying(player, mediaItem, streamRequest.adsId)) {
if (state == Player.STATE_ENDED && isCurrentAdPlaying(player, mediaItem, adsId)) {
streamPlayer.onContentCompleted();
}
}
@Override
public void onVolumeChanged(float volume) {
if (!isCurrentAdPlaying(player, mediaItem, streamRequest.adsId)) {
if (!isCurrentAdPlaying(player, mediaItem, adsId)) {
return;
}
int volumePct = (int) Math.floor(volume * 100);
......@@ -690,15 +695,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
switch (event.getType()) {
case CUEPOINTS_CHANGED:
// CUEPOINTS_CHANGED event is firing multiple times with the same queue points.
if (!streamRequest.isLiveStream() && newAdPlaybackState.equals(AdPlaybackState.NONE)) {
if (!isLiveStream && newAdPlaybackState.equals(AdPlaybackState.NONE)) {
newAdPlaybackState =
setVodAdGroupPlaceholders(
checkNotNull(streamManager).getCuePoints(),
new AdPlaybackState(streamRequest.adsId));
checkNotNull(streamManager).getCuePoints(), new AdPlaybackState(adsId));
}
break;
case LOADED:
if (streamRequest.isLiveStream()) {
if (isLiveStream) {
Timeline timeline = player.getCurrentTimeline();
Timeline.Window window =
timeline.getWindow(player.getCurrentMediaItemIndex(), new Timeline.Window());
......@@ -715,14 +719,14 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
event.getAd(),
currentPeriodPosition,
newAdPlaybackState.equals(AdPlaybackState.NONE)
? new AdPlaybackState(streamRequest.adsId)
? new AdPlaybackState(adsId)
: newAdPlaybackState);
} else {
newAdPlaybackState = setVodAdInPlaceholder(event.getAd(), newAdPlaybackState);
}
break;
case SKIPPED:
if (!streamRequest.isLiveStream()) {
if (!isLiveStream) {
newAdPlaybackState = skipAd(event.getAd(), newAdPlaybackState);
}
break;
......@@ -740,7 +744,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
mainHandler.post(() -> setContentTimeline(contentTimeline));
// Defer source refresh to ad playback state update for VOD. Refresh immediately when live
// with single period.
return !streamRequest.isLiveStream() || contentTimeline.getPeriodCount() > 1;
return !isLiveStream || contentTimeline.getPeriodCount() > 1;
}
}
......
......@@ -27,225 +27,20 @@ import com.google.ads.interactivemedia.v3.api.StreamRequest;
import com.google.ads.interactivemedia.v3.api.StreamRequest.StreamFormat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.ContentType;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.Map;
/** Stream request data for an IMA DAI stream. */
/* package */ final class ServerSideAdInsertionStreamRequest {
/**
* Builder for URI for IMA DAI streams. The resulting URI can be used to build a {@link
* com.google.android.exoplayer2.MediaItem#fromUri(Uri) media item} that can be played by the {@link
* ImaServerSideAdInsertionMediaSource}.
*/
public final class ImaServerSideAdInsertionUriBuilder {
/** The default timeout for loading the video URI, in milliseconds. */
public static final int DEFAULT_LOAD_VIDEO_TIMEOUT_MS = 10_000;
/** Builds a {@link ServerSideAdInsertionStreamRequest}. */
public static final class Builder {
@Nullable private String adsId;
@Nullable private String assetKey;
@Nullable private String apiKey;
@Nullable private String contentSourceId;
@Nullable private String videoId;
@Nullable private String manifestSuffix;
@Nullable private String contentUrl;
@Nullable private String authToken;
@Nullable private String streamActivityMonitorId;
private ImmutableMap<String, String> adTagParameters;
public @ContentType int format = C.TYPE_HLS;
private int loadVideoTimeoutMs;
/** Creates a new instance. */
public Builder() {
adTagParameters = ImmutableMap.of();
loadVideoTimeoutMs = DEFAULT_LOAD_VIDEO_TIMEOUT_MS;
}
/**
* An opaque identifier for associated ad playback state, or {@code null} if the {@link
* #setAssetKey(String) asset key} (for live) or {@link #setVideoId(String) video id} (for VOD)
* should be used as the ads identifier.
*
* @param adsId The ads identifier.
* @return This instance, for convenience.
*/
public Builder setAdsId(String adsId) {
this.adsId = adsId;
return this;
}
/**
* The stream request asset key used for live streams.
*
* @param assetKey Live stream asset key.
* @return This instance, for convenience.
*/
public Builder setAssetKey(@Nullable String assetKey) {
this.assetKey = assetKey;
return this;
}
/**
* Sets the stream request authorization token. Used in place of {@link #setApiKey(String) the
* API key} for stricter content authorization. The publisher can control individual content
* streams authorizations based on this token.
*
* @param authToken Live stream authorization token.
* @return This instance, for convenience.
*/
public Builder setAuthToken(@Nullable String authToken) {
this.authToken = authToken;
return this;
}
/**
* The stream request content source ID used for on-demand streams.
*
* @param contentSourceId VOD stream content source id.
* @return This instance, for convenience.
*/
public Builder setContentSourceId(@Nullable String contentSourceId) {
this.contentSourceId = contentSourceId;
return this;
}
/**
* The stream request video ID used for on-demand streams.
*
* @param videoId VOD stream video id.
* @return This instance, for convenience.
*/
public Builder setVideoId(@Nullable String videoId) {
this.videoId = videoId;
return this;
}
/**
* Sets the format of the stream request.
*
* @param format VOD or live stream type.
* @return This instance, for convenience.
*/
public Builder setFormat(@ContentType int format) {
checkArgument(format == C.TYPE_DASH || format == C.TYPE_HLS);
this.format = format;
return this;
}
/**
* The stream request API key. This is used for content authentication. The API key is provided
* to the publisher to unlock their content. It's a security measure used to verify the
* applications that are attempting to access the content.
*
* @param apiKey Stream api key.
* @return This instance, for convenience.
*/
public Builder setApiKey(@Nullable String apiKey) {
this.apiKey = apiKey;
return this;
}
/**
* Sets the ID to be used to debug the stream with the stream activity monitor. This is used to
* provide a convenient way to allow publishers to find a stream log in the stream activity
* monitor tool.
*
* @param streamActivityMonitorId ID for debugging the stream with the stream activity monitor.
* @return This instance, for convenience.
*/
public Builder setStreamActivityMonitorId(@Nullable String streamActivityMonitorId) {
this.streamActivityMonitorId = streamActivityMonitorId;
return this;
}
/**
* Sets the overridable ad tag parameters on the stream request. <a
* href="//support.google.com/dfp_premium/answer/7320899">Supply targeting parameters to your
* stream</a> provides more information.
*
* <p>You can use the dai-ot and dai-ov parameters for stream variant preference. See <a
* href="//support.google.com/dfp_premium/answer/7320898">Override Stream Variant Parameters</a>
* for more information.
*
* @param adTagParameters A map of extra parameters to pass to the ad server.
* @return This instance, for convenience.
*/
public Builder setAdTagParameters(Map<String, String> adTagParameters) {
this.adTagParameters = ImmutableMap.copyOf(adTagParameters);
return this;
}
/**
* Sets the optional stream manifest's suffix, which will be appended to the stream manifest's
* URL. The provided string must be URL-encoded and must not include a leading question mark.
*
* @param manifestSuffix Stream manifest's suffix.
* @return This instance, for convenience.
*/
public Builder setManifestSuffix(@Nullable String manifestSuffix) {
this.manifestSuffix = manifestSuffix;
return this;
}
/**
* Specifies the deep link to the content's screen. If provided, this parameter is passed to the
* OM SDK. See <a href="//developer.android.com/training/app-links/deep-linking">Android
* documentation</a> for more information.
*
* @param contentUrl Deep link to the content's screen.
* @return This instance, for convenience.
*/
public Builder setContentUrl(@Nullable String contentUrl) {
this.contentUrl = contentUrl;
return this;
}
/**
* Sets the duration after which resolving the video URI should time out, in milliseconds.
*
* <p>The default is {@link #DEFAULT_LOAD_VIDEO_TIMEOUT_MS} milliseconds.
*
* @param loadVideoTimeoutMs The timeout after which to give up resolving the video URI.
* @return This instance, for convenience.
*/
public Builder setLoadVideoTimeoutMs(int loadVideoTimeoutMs) {
this.loadVideoTimeoutMs = loadVideoTimeoutMs;
return this;
}
/**
* Builds a {@link ServerSideAdInsertionStreamRequest} with the builder's current values.
*
* @return The build {@link ServerSideAdInsertionStreamRequest}.
* @throws IllegalStateException If request has missing or invalid inputs.
*/
public ServerSideAdInsertionStreamRequest build() {
checkState(
(TextUtils.isEmpty(assetKey)
&& !TextUtils.isEmpty(contentSourceId)
&& !TextUtils.isEmpty(videoId))
|| (!TextUtils.isEmpty(assetKey)
&& TextUtils.isEmpty(contentSourceId)
&& TextUtils.isEmpty(videoId)));
@Nullable String adsId = this.adsId;
if (adsId == null) {
adsId = assetKey != null ? assetKey : checkNotNull(videoId);
}
return new ServerSideAdInsertionStreamRequest(
adsId,
assetKey,
apiKey,
contentSourceId,
videoId,
adTagParameters,
manifestSuffix,
contentUrl,
authToken,
streamActivityMonitorId,
format,
loadVideoTimeoutMs);
}
}
private static final String IMA_AUTHORITY = "dai.google.com";
private static final String ADS_ID = "adsId";
private static final String ASSET_KEY = "assetKey";
......@@ -260,86 +55,199 @@ import java.util.Map;
private static final String FORMAT = "format";
private static final String LOAD_VIDEO_TIMEOUT_MS = "loadVideoTimeoutMs";
public final String adsId;
@Nullable public final String assetKey;
@Nullable public final String apiKey;
@Nullable public final String contentSourceId;
@Nullable public final String videoId;
public final ImmutableMap<String, String> adTagParameters;
@Nullable public final String manifestSuffix;
@Nullable public final String contentUrl;
@Nullable public final String authToken;
@Nullable public final String streamActivityMonitorId;
public @ContentType int format = C.TYPE_HLS;
public final int loadVideoTimeoutMs;
@Nullable private String adsId;
@Nullable private String assetKey;
@Nullable private String apiKey;
@Nullable private String contentSourceId;
@Nullable private String videoId;
@Nullable private String manifestSuffix;
@Nullable private String contentUrl;
@Nullable private String authToken;
@Nullable private String streamActivityMonitorId;
private ImmutableMap<String, String> adTagParameters;
public @ContentType int format;
private int loadVideoTimeoutMs;
private ServerSideAdInsertionStreamRequest(
String adsId,
@Nullable String assetKey,
@Nullable String apiKey,
@Nullable String contentSourceId,
@Nullable String videoId,
ImmutableMap<String, String> adTagParameters,
@Nullable String manifestSuffix,
@Nullable String contentUrl,
@Nullable String authToken,
@Nullable String streamActivityMonitorId,
@ContentType int format,
int loadVideoTimeoutMs) {
/** Creates a new instance. */
public ImaServerSideAdInsertionUriBuilder() {
adTagParameters = ImmutableMap.of();
loadVideoTimeoutMs = DEFAULT_LOAD_VIDEO_TIMEOUT_MS;
format = C.TYPE_OTHER;
}
/**
* An opaque identifier for associated ad playback state, or {@code null} if the {@link
* #setAssetKey(String) asset key} (for live) or {@link #setVideoId(String) video id} (for VOD)
* should be used as the ads identifier.
*
* @param adsId The ads identifier.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setAdsId(String adsId) {
this.adsId = adsId;
return this;
}
/**
* The stream request asset key used for live streams.
*
* @param assetKey Live stream asset key.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setAssetKey(@Nullable String assetKey) {
this.assetKey = assetKey;
this.apiKey = apiKey;
return this;
}
/**
* Sets the stream request authorization token. Used in place of {@link #setApiKey(String) the API
* key} for stricter content authorization. The publisher can control individual content streams
* authorizations based on this token.
*
* @param authToken Live stream authorization token.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setAuthToken(@Nullable String authToken) {
this.authToken = authToken;
return this;
}
/**
* The stream request content source ID used for on-demand streams.
*
* @param contentSourceId VOD stream content source id.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setContentSourceId(@Nullable String contentSourceId) {
this.contentSourceId = contentSourceId;
return this;
}
/**
* The stream request video ID used for on-demand streams.
*
* @param videoId VOD stream video id.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setVideoId(@Nullable String videoId) {
this.videoId = videoId;
this.adTagParameters = adTagParameters;
this.manifestSuffix = manifestSuffix;
this.contentUrl = contentUrl;
this.authToken = authToken;
this.streamActivityMonitorId = streamActivityMonitorId;
return this;
}
/**
* Sets the format of the stream request.
*
* @param format VOD or live stream type.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setFormat(@ContentType int format) {
checkArgument(format == C.TYPE_DASH || format == C.TYPE_HLS);
this.format = format;
this.loadVideoTimeoutMs = loadVideoTimeoutMs;
return this;
}
/** Returns whether this request is for a live stream or false if it is a VOD stream. */
public boolean isLiveStream() {
return !TextUtils.isEmpty(assetKey);
/**
* The stream request API key. This is used for content authentication. The API key is provided to
* the publisher to unlock their content. It's a security measure used to verify the applications
* that are attempting to access the content.
*
* @param apiKey Stream api key.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setApiKey(@Nullable String apiKey) {
this.apiKey = apiKey;
return this;
}
/** Returns the corresponding {@link StreamRequest}. */
@SuppressWarnings("nullness") // Required for making nullness test pass for library_with_ima_sdk.
public StreamRequest getStreamRequest() {
StreamRequest streamRequest;
if (!TextUtils.isEmpty(assetKey)) {
streamRequest = ImaSdkFactory.getInstance().createLiveStreamRequest(assetKey, apiKey);
} else {
streamRequest =
ImaSdkFactory.getInstance()
.createVodStreamRequest(checkNotNull(contentSourceId), checkNotNull(videoId), apiKey);
}
if (format == C.TYPE_DASH) {
streamRequest.setFormat(StreamFormat.DASH);
} else if (format == C.TYPE_HLS) {
streamRequest.setFormat(StreamFormat.HLS);
}
// Optional params.
streamRequest.setAdTagParameters(adTagParameters);
if (manifestSuffix != null) {
streamRequest.setManifestSuffix(manifestSuffix);
}
if (contentUrl != null) {
streamRequest.setContentUrl(contentUrl);
}
if (authToken != null) {
streamRequest.setAuthToken(authToken);
}
if (streamActivityMonitorId != null) {
streamRequest.setStreamActivityMonitorId(streamActivityMonitorId);
}
return streamRequest;
/**
* Sets the ID to be used to debug the stream with the stream activity monitor. This is used to
* provide a convenient way to allow publishers to find a stream log in the stream activity
* monitor tool.
*
* @param streamActivityMonitorId ID for debugging the stream with the stream activity monitor.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setStreamActivityMonitorId(
@Nullable String streamActivityMonitorId) {
this.streamActivityMonitorId = streamActivityMonitorId;
return this;
}
/**
* Sets the overridable ad tag parameters on the stream request. <a
* href="//support.google.com/dfp_premium/answer/7320899">Supply targeting parameters to your
* stream</a> provides more information.
*
* <p>You can use the dai-ot and dai-ov parameters for stream variant preference. See <a
* href="//support.google.com/dfp_premium/answer/7320898">Override Stream Variant Parameters</a>
* for more information.
*
* @param adTagParameters A map of extra parameters to pass to the ad server.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setAdTagParameters(
Map<String, String> adTagParameters) {
this.adTagParameters = ImmutableMap.copyOf(adTagParameters);
return this;
}
/**
* Sets the optional stream manifest's suffix, which will be appended to the stream manifest's
* URL. The provided string must be URL-encoded and must not include a leading question mark.
*
* @param manifestSuffix Stream manifest's suffix.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setManifestSuffix(@Nullable String manifestSuffix) {
this.manifestSuffix = manifestSuffix;
return this;
}
/**
* Specifies the deep link to the content's screen. If provided, this parameter is passed to the
* OM SDK. See <a href="//developer.android.com/training/app-links/deep-linking">Android
* documentation</a> for more information.
*
* @param contentUrl Deep link to the content's screen.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setContentUrl(@Nullable String contentUrl) {
this.contentUrl = contentUrl;
return this;
}
/**
* Sets the duration after which resolving the video URI should time out, in milliseconds.
*
* <p>The default is {@link #DEFAULT_LOAD_VIDEO_TIMEOUT_MS} milliseconds.
*
* @param loadVideoTimeoutMs The timeout after which to give up resolving the video URI.
* @return This instance, for convenience.
*/
public ImaServerSideAdInsertionUriBuilder setLoadVideoTimeoutMs(int loadVideoTimeoutMs) {
this.loadVideoTimeoutMs = loadVideoTimeoutMs;
return this;
}
/** Returns a corresponding {@link Uri}. */
public Uri toUri() {
/**
* Builds a URI with the builder's current values.
*
* @return The build {@link Uri}.
* @throws IllegalStateException If the builder has missing or invalid inputs.
*/
public Uri build() {
checkState(
(TextUtils.isEmpty(assetKey)
&& !TextUtils.isEmpty(contentSourceId)
&& !TextUtils.isEmpty(videoId))
|| (!TextUtils.isEmpty(assetKey)
&& TextUtils.isEmpty(contentSourceId)
&& TextUtils.isEmpty(videoId)));
checkState(format != C.TYPE_OTHER);
@Nullable String adsId = this.adsId;
if (adsId == null) {
adsId = assetKey != null ? assetKey : checkNotNull(videoId);
}
Uri.Builder dataUriBuilder = new Uri.Builder();
dataUriBuilder.scheme(C.SSAI_SCHEME);
dataUriBuilder.authority(IMA_AUTHORITY);
......@@ -384,94 +292,80 @@ import java.util.Map;
return dataUriBuilder.build();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ServerSideAdInsertionStreamRequest)) {
return false;
}
ServerSideAdInsertionStreamRequest that = (ServerSideAdInsertionStreamRequest) o;
return format == that.format
&& loadVideoTimeoutMs == that.loadVideoTimeoutMs
&& Objects.equal(adsId, that.adsId)
&& Objects.equal(assetKey, that.assetKey)
&& Objects.equal(apiKey, that.apiKey)
&& Objects.equal(contentSourceId, that.contentSourceId)
&& Objects.equal(videoId, that.videoId)
&& Objects.equal(adTagParameters, that.adTagParameters)
&& Objects.equal(manifestSuffix, that.manifestSuffix)
&& Objects.equal(contentUrl, that.contentUrl)
&& Objects.equal(authToken, that.authToken)
&& Objects.equal(streamActivityMonitorId, that.streamActivityMonitorId);
/** Returns whether the provided request is for a live stream or false if it is a VOD stream. */
/* package */ static boolean isLiveStream(Uri uri) {
return !TextUtils.isEmpty(uri.getQueryParameter(ASSET_KEY));
}
@Override
public int hashCode() {
return Objects.hashCode(
adsId,
assetKey,
apiKey,
contentSourceId,
videoId,
adTagParameters,
manifestSuffix,
contentUrl,
authToken,
streamActivityMonitorId,
loadVideoTimeoutMs,
format);
/** Returns the opaque adsId for this stream. */
/* package */ static String getAdsId(Uri uri) {
return checkNotNull(uri.getQueryParameter(ADS_ID));
}
/**
* Creates a {@link ServerSideAdInsertionStreamRequest} for the given URI.
*
* @param uri The URI.
* @return An {@link ServerSideAdInsertionStreamRequest} for the given URI.
* @throws IllegalStateException If uri has missing or invalid inputs.
*/
public static ServerSideAdInsertionStreamRequest fromUri(Uri uri) {
ServerSideAdInsertionStreamRequest.Builder request =
new ServerSideAdInsertionStreamRequest.Builder();
/** Returns the video load timeout in milliseconds. */
/* package */ static int getLoadVideoTimeoutMs(Uri uri) {
@Nullable String adsLoaderTimeoutUs = uri.getQueryParameter(LOAD_VIDEO_TIMEOUT_MS);
return TextUtils.isEmpty(adsLoaderTimeoutUs)
? DEFAULT_LOAD_VIDEO_TIMEOUT_MS
: Integer.parseInt(adsLoaderTimeoutUs);
}
/** Returns the corresponding {@link StreamRequest}. */
@SuppressWarnings("nullness") // Required for making nullness test pass for library_with_ima_sdk.
/* package */ static StreamRequest createStreamRequest(Uri uri) {
if (!C.SSAI_SCHEME.equals(uri.getScheme()) || !IMA_AUTHORITY.equals(uri.getAuthority())) {
throw new IllegalArgumentException("Invalid URI scheme or authority.");
}
request.setAdsId(checkNotNull(uri.getQueryParameter(ADS_ID)));
request.setAssetKey(uri.getQueryParameter(ASSET_KEY));
request.setApiKey(uri.getQueryParameter(API_KEY));
request.setContentSourceId(uri.getQueryParameter(CONTENT_SOURCE_ID));
request.setVideoId(uri.getQueryParameter(VIDEO_ID));
request.setManifestSuffix(uri.getQueryParameter(MANIFEST_SUFFIX));
request.setContentUrl(uri.getQueryParameter(CONTENT_URL));
request.setAuthToken(uri.getQueryParameter(AUTH_TOKEN));
request.setStreamActivityMonitorId(uri.getQueryParameter(STREAM_ACTIVITY_MONITOR_ID));
String adsLoaderTimeoutUs = uri.getQueryParameter(LOAD_VIDEO_TIMEOUT_MS);
request.setLoadVideoTimeoutMs(
TextUtils.isEmpty(adsLoaderTimeoutUs)
? DEFAULT_LOAD_VIDEO_TIMEOUT_MS
: Integer.parseInt(adsLoaderTimeoutUs));
String formatValue = uri.getQueryParameter(FORMAT);
if (!TextUtils.isEmpty(formatValue)) {
request.setFormat(Integer.parseInt(formatValue));
StreamRequest streamRequest;
// Required params.
@Nullable String assetKey = uri.getQueryParameter(ASSET_KEY);
@Nullable String apiKey = uri.getQueryParameter(API_KEY);
@Nullable String contentSourceId = uri.getQueryParameter(CONTENT_SOURCE_ID);
@Nullable String videoId = uri.getQueryParameter(VIDEO_ID);
if (!TextUtils.isEmpty(assetKey)) {
streamRequest = ImaSdkFactory.getInstance().createLiveStreamRequest(assetKey, apiKey);
} else {
streamRequest =
ImaSdkFactory.getInstance()
.createVodStreamRequest(checkNotNull(contentSourceId), checkNotNull(videoId), apiKey);
}
Map<String, String> adTagParameters;
String adTagParametersValue;
String singleAdTagParameterValue;
if (uri.getQueryParameter(AD_TAG_PARAMETERS) != null) {
adTagParameters = new HashMap<>();
adTagParametersValue = uri.getQueryParameter(AD_TAG_PARAMETERS);
if (!TextUtils.isEmpty(adTagParametersValue)) {
Uri adTagParametersUri = Uri.parse(adTagParametersValue);
for (String paramName : adTagParametersUri.getQueryParameterNames()) {
singleAdTagParameterValue = adTagParametersUri.getQueryParameter(paramName);
if (!TextUtils.isEmpty(singleAdTagParameterValue)) {
adTagParameters.put(paramName, singleAdTagParameterValue);
}
int format = Integer.parseInt(uri.getQueryParameter(FORMAT));
if (format == C.TYPE_DASH) {
streamRequest.setFormat(StreamFormat.DASH);
} else if (format == C.TYPE_HLS) {
streamRequest.setFormat(StreamFormat.HLS);
} else {
throw new IllegalArgumentException("Unsupported stream format:" + format);
}
// Optional params.
@Nullable String adTagParametersValue = uri.getQueryParameter(AD_TAG_PARAMETERS);
if (!TextUtils.isEmpty(adTagParametersValue)) {
Map<String, String> adTagParameters = new HashMap<>();
Uri adTagParametersUri = Uri.parse(adTagParametersValue);
for (String paramName : adTagParametersUri.getQueryParameterNames()) {
String singleAdTagParameterValue = adTagParametersUri.getQueryParameter(paramName);
if (!TextUtils.isEmpty(singleAdTagParameterValue)) {
adTagParameters.put(paramName, singleAdTagParameterValue);
}
}
request.setAdTagParameters(adTagParameters);
streamRequest.setAdTagParameters(adTagParameters);
}
@Nullable String manifestSuffix = uri.getQueryParameter(MANIFEST_SUFFIX);
if (manifestSuffix != null) {
streamRequest.setManifestSuffix(manifestSuffix);
}
return request.build();
@Nullable String contentUrl = uri.getQueryParameter(CONTENT_URL);
if (contentUrl != null) {
streamRequest.setContentUrl(contentUrl);
}
@Nullable String authToken = uri.getQueryParameter(AUTH_TOKEN);
if (authToken != null) {
streamRequest.setAuthToken(authToken);
}
@Nullable String streamActivityMonitorId = uri.getQueryParameter(STREAM_ACTIVITY_MONITOR_ID);
if (streamActivityMonitorId != null) {
streamRequest.setStreamActivityMonitorId(streamActivityMonitorId);
}
return streamRequest;
}
}
......@@ -17,16 +17,20 @@ package com.google.android.exoplayer2.ext.ima;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.ads.interactivemedia.v3.api.StreamRequest;
import com.google.ads.interactivemedia.v3.api.StreamRequest.StreamFormat;
import com.google.android.exoplayer2.C;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link ServerSideAdInsertionStreamRequest}. */
/** Unit tests for {@link ImaServerSideAdInsertionUriBuilder}. */
@RunWith(AndroidJUnit4.class)
public final class ServerSideAdInsertionStreamRequestTest {
public final class ImaServerSideAdInsertionUriBuilderTest {
private static final String ADS_ID = "testAdsId";
private static final String ASSET_KEY = "testAssetKey";
......@@ -39,8 +43,6 @@ public final class ServerSideAdInsertionStreamRequestTest {
private static final String AUTH_TOKEN = "testAuthToken";
private static final String STREAM_ACTIVITY_MONITOR_ID = "testStreamActivityMonitorId";
private static final int ADS_LOADER_TIMEOUT_MS = 2;
private static final int FORMAT_DASH = 0;
private static final int FORMAT_HLS = 2;
private static final Map<String, String> adTagParameters = new HashMap<>();
static {
......@@ -49,9 +51,8 @@ public final class ServerSideAdInsertionStreamRequestTest {
}
@Test
public void build_live_correctUriAndParsing() {
ServerSideAdInsertionStreamRequest.Builder builder =
new ServerSideAdInsertionStreamRequest.Builder();
public void build_live_correctUriParsing() {
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
builder.setAdsId(ADS_ID);
builder.setAssetKey(ASSET_KEY);
builder.setApiKey(API_KEY);
......@@ -59,21 +60,34 @@ public final class ServerSideAdInsertionStreamRequestTest {
builder.setContentUrl(CONTENT_URL);
builder.setAuthToken(AUTH_TOKEN);
builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID);
builder.setFormat(FORMAT_HLS);
builder.setFormat(C.TYPE_HLS);
builder.setAdTagParameters(adTagParameters);
builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS);
ServerSideAdInsertionStreamRequest streamRequest = builder.build();
ServerSideAdInsertionStreamRequest requestAfterConversions =
ServerSideAdInsertionStreamRequest.fromUri(streamRequest.toUri());
assertThat(streamRequest).isEqualTo(requestAfterConversions);
Uri uri = builder.build();
StreamRequest streamRequest = ImaServerSideAdInsertionUriBuilder.createStreamRequest(uri);
assertThat(streamRequest.getAssetKey()).isEqualTo(ASSET_KEY);
assertThat(streamRequest.getApiKey()).isEqualTo(API_KEY);
assertThat(streamRequest.getManifestSuffix()).isEqualTo(MANIFEST_SUFFIX);
assertThat(streamRequest.getContentUrl()).isEqualTo(CONTENT_URL);
assertThat(streamRequest.getAuthToken()).isEqualTo(AUTH_TOKEN);
assertThat(streamRequest.getStreamActivityMonitorId()).isEqualTo(STREAM_ACTIVITY_MONITOR_ID);
assertThat(streamRequest.getFormat()).isEqualTo(StreamFormat.HLS);
assertThat(streamRequest.getAdTagParameters()).isEqualTo(adTagParameters);
boolean isLive = ImaServerSideAdInsertionUriBuilder.isLiveStream(uri);
assertThat(isLive).isTrue();
String adsId = ImaServerSideAdInsertionUriBuilder.getAdsId(uri);
assertThat(adsId).isEqualTo(ADS_ID);
int loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(uri);
assertThat(loadVideoTimeoutMs).isEqualTo(ADS_LOADER_TIMEOUT_MS);
}
@Test
public void build_vod_correctUriAndParsing() {
ServerSideAdInsertionStreamRequest.Builder builder =
new ServerSideAdInsertionStreamRequest.Builder();
public void build_vod_correctUriParsing() {
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
builder.setAdsId(ADS_ID);
builder.setApiKey(API_KEY);
builder.setContentSourceId(CONTENT_SOURCE_ID);
......@@ -82,46 +96,60 @@ public final class ServerSideAdInsertionStreamRequestTest {
builder.setContentUrl(CONTENT_URL);
builder.setAuthToken(AUTH_TOKEN);
builder.setStreamActivityMonitorId(STREAM_ACTIVITY_MONITOR_ID);
builder.setFormat(FORMAT_DASH);
builder.setFormat(C.TYPE_DASH);
builder.setAdTagParameters(adTagParameters);
builder.setLoadVideoTimeoutMs(ADS_LOADER_TIMEOUT_MS);
ServerSideAdInsertionStreamRequest streamRequest = builder.build();
ServerSideAdInsertionStreamRequest requestAfterConversions =
ServerSideAdInsertionStreamRequest.fromUri(streamRequest.toUri());
assertThat(requestAfterConversions).isEqualTo(streamRequest);
Uri uri = builder.build();
StreamRequest streamRequest = ImaServerSideAdInsertionUriBuilder.createStreamRequest(uri);
assertThat(streamRequest.getApiKey()).isEqualTo(API_KEY);
assertThat(streamRequest.getContentSourceId()).isEqualTo(CONTENT_SOURCE_ID);
assertThat(streamRequest.getVideoId()).isEqualTo(VIDEO_ID);
assertThat(streamRequest.getManifestSuffix()).isEqualTo(MANIFEST_SUFFIX);
assertThat(streamRequest.getContentUrl()).isEqualTo(CONTENT_URL);
assertThat(streamRequest.getAuthToken()).isEqualTo(AUTH_TOKEN);
assertThat(streamRequest.getStreamActivityMonitorId()).isEqualTo(STREAM_ACTIVITY_MONITOR_ID);
assertThat(streamRequest.getFormat()).isEqualTo(StreamFormat.DASH);
assertThat(streamRequest.getAdTagParameters()).isEqualTo(adTagParameters);
boolean isLive = ImaServerSideAdInsertionUriBuilder.isLiveStream(uri);
assertThat(isLive).isFalse();
String adsId = ImaServerSideAdInsertionUriBuilder.getAdsId(uri);
assertThat(adsId).isEqualTo(ADS_ID);
int loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(uri);
assertThat(loadVideoTimeoutMs).isEqualTo(ADS_LOADER_TIMEOUT_MS);
}
@Test
public void build_vodWithNoAdsId_usesVideoIdAsDefault() {
ServerSideAdInsertionStreamRequest.Builder builder =
new ServerSideAdInsertionStreamRequest.Builder();
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
builder.setContentSourceId(CONTENT_SOURCE_ID);
builder.setVideoId(VIDEO_ID);
builder.setFormat(C.TYPE_DASH);
ServerSideAdInsertionStreamRequest streamRequest = builder.build();
Uri streamRequest = builder.build();
assertThat(streamRequest.adsId).isEqualTo(VIDEO_ID);
assertThat(streamRequest.toUri().getQueryParameter("adsId")).isEqualTo(VIDEO_ID);
assertThat(ImaServerSideAdInsertionUriBuilder.getAdsId(streamRequest)).isEqualTo(VIDEO_ID);
assertThat(streamRequest.getQueryParameter("adsId")).isEqualTo(VIDEO_ID);
}
@Test
public void build_liveWithNoAdsId_usesAssetKeyAsDefault() {
ServerSideAdInsertionStreamRequest.Builder builder =
new ServerSideAdInsertionStreamRequest.Builder();
ImaServerSideAdInsertionUriBuilder builder = new ImaServerSideAdInsertionUriBuilder();
builder.setAssetKey(ASSET_KEY);
builder.setFormat(C.TYPE_DASH);
ServerSideAdInsertionStreamRequest streamRequest = builder.build();
Uri streamRequest = builder.build();
assertThat(streamRequest.adsId).isEqualTo(ASSET_KEY);
assertThat(streamRequest.toUri().getQueryParameter("adsId")).isEqualTo(ASSET_KEY);
assertThat(ImaServerSideAdInsertionUriBuilder.getAdsId(streamRequest)).isEqualTo(ASSET_KEY);
assertThat(streamRequest.getQueryParameter("adsId")).isEqualTo(ASSET_KEY);
}
@Test
public void build_assetKeyWithVideoId_throwsIllegalStateException() {
ServerSideAdInsertionStreamRequest.Builder requestBuilder =
new ServerSideAdInsertionStreamRequest.Builder();
ImaServerSideAdInsertionUriBuilder requestBuilder = new ImaServerSideAdInsertionUriBuilder();
requestBuilder.setAssetKey(ASSET_KEY);
requestBuilder.setVideoId(VIDEO_ID);
......@@ -130,8 +158,7 @@ public final class ServerSideAdInsertionStreamRequestTest {
@Test
public void build_assetKeyWithContentSource_throwsIllegalStateException() {
ServerSideAdInsertionStreamRequest.Builder requestBuilder =
new ServerSideAdInsertionStreamRequest.Builder();
ImaServerSideAdInsertionUriBuilder requestBuilder = new ImaServerSideAdInsertionUriBuilder();
requestBuilder.setAssetKey(ASSET_KEY);
requestBuilder.setContentSourceId(CONTENT_SOURCE_ID);
......@@ -140,9 +167,21 @@ public final class ServerSideAdInsertionStreamRequestTest {
@Test
public void build_withoutContentSourceAndVideoIdOrAssetKey_throwsIllegalStateException() {
ServerSideAdInsertionStreamRequest.Builder requestBuilder =
new ServerSideAdInsertionStreamRequest.Builder();
ImaServerSideAdInsertionUriBuilder requestBuilder = new ImaServerSideAdInsertionUriBuilder();
Assert.assertThrows(IllegalStateException.class, requestBuilder::build);
}
@Test
public void build_withoutLoadVideoTimeoutMs_usesDefaultTimeout() {
Uri uri =
new ImaServerSideAdInsertionUriBuilder()
.setAssetKey(ASSET_KEY)
.setFormat(C.TYPE_DASH)
.build();
int loadVideoTimeoutMs = ImaServerSideAdInsertionUriBuilder.getLoadVideoTimeoutMs(uri);
assertThat(loadVideoTimeoutMs)
.isEqualTo(ImaServerSideAdInsertionUriBuilder.DEFAULT_LOAD_VIDEO_TIMEOUT_MS);
}
}
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