diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..1f06fa96 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "orchestrator/android/ExoPlayer-Yospace-Java/ssp"] + path = orchestrator/android/ExoPlayer-Yospace-Java/ssp + url = git@github.com:streamroot/streamroot-samples-private.git diff --git a/orchestrator/android/AllSamples/settings.gradle b/orchestrator/android/AllSamples/settings.gradle index 01fccca3..f110c7f0 100644 --- a/orchestrator/android/AllSamples/settings.gradle +++ b/orchestrator/android/AllSamples/settings.gradle @@ -2,7 +2,8 @@ def samples = [ 'ExoPlayer', 'ExoPlayer-Java', - 'PRESTOplay-Java' + 'PRESTOplay-Java', + 'ExoPlayer-Yospace-Java' ] samples.each { diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/.gitignore b/orchestrator/android/ExoPlayer-Yospace-Java/.gitignore new file mode 100644 index 00000000..5edb4eeb --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/README.md b/orchestrator/android/ExoPlayer-Yospace-Java/README.md new file mode 100644 index 00000000..d3ad9eb2 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/README.md @@ -0,0 +1,34 @@ +# Streamroot Android Orchestrator ExoPlayer Yospace Java + +## Common integration + +Make sure you start with the [common Java integration](https://github.com/streamroot/streamroot-samples/blob/master/orchestrator/android/README.md) + +## Specific integration override + +### 3. Bridge between your Player and the delivery client. + +In order to work perfectly, the SDK instances need to interact with the player and listen to its events. +Please add the following class to your project : + +- [PlayerInteractor](https://github.com/streamroot/streamroot-samples/blob/master/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/ExoPlayerInteractor.java) + +## Additional steps + +-> ExoPlayer requires targetCompatibility java 8. +-> URLs are hardcoded in the YospaceModule and mode is hardcoded in PlayerActivity + +```java +private static final Session.PlaybackMode YOSPACE_MODE = Session.PlaybackMode.LIVE; +``` + +-> Some files are private and thus symlinked. You can find the missing files (AARs, adapters, policy impl, etc), inside the Yospace SDK sample application. + +## Integrate with PRESTOplay + +- [PRESTOplay-Java project](https://github.com/streamroot/streamroot-samples/tree/master/orchestrator/android/PRESTOplay-Java) + +In particular, make sure you replace the player interactor by PRESTO's : + +- [PlayerInteractor](https://github.com/streamroot/streamroot-samples/blob/master/orchestrator/android/PRESTOplay-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/prestoplay/PRESTOPlayerInteractor.java) + diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/.gitignore b/orchestrator/android/ExoPlayer-Yospace-Java/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/build.gradle b/orchestrator/android/ExoPlayer-Yospace-Java/app/build.gradle new file mode 100644 index 00000000..eb3f1578 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/build.gradle @@ -0,0 +1,46 @@ +//noinspection GradleCompatible +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + defaultConfig { + applicationId "io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer" + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + multiDexEnabled true + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + flatDir { + dirs 'libs' + } +} + +dependencies { + implementation 'com.google.android.exoplayer:exoplayer:2.11.7' + + def dc_version = '1.1.1' + implementation 'io.streamroot.lumen.delivery.client:orchestrator-sdk:' + dc_version + implementation 'io.streamroot.lumen.delivery.client:orchestrator-sdk-utils:' + dc_version + + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.2.1' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + + implementation(name: 'yoAdManagement-debug', ext: 'aar') + implementation(name: 'yoUtil-debug', ext: 'aar') +} diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/libs/yoAdManagement-debug.aar b/orchestrator/android/ExoPlayer-Yospace-Java/app/libs/yoAdManagement-debug.aar new file mode 120000 index 00000000..307f96ea --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/libs/yoAdManagement-debug.aar @@ -0,0 +1 @@ +../../ssp/exoplayer-yospace/lib/yoAdManagement-debug.aar \ No newline at end of file diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/libs/yoUtil-debug.aar b/orchestrator/android/ExoPlayer-Yospace-Java/app/libs/yoUtil-debug.aar new file mode 120000 index 00000000..a28feafd --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/libs/yoUtil-debug.aar @@ -0,0 +1 @@ +../../ssp/exoplayer-yospace/lib/yoUtil-debug.aar \ No newline at end of file diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/proguard-rules.pro b/orchestrator/android/ExoPlayer-Yospace-Java/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/AndroidManifest.xml b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9bfd57ff --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/Callback1.java b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/Callback1.java new file mode 100644 index 00000000..36b4123b --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/Callback1.java @@ -0,0 +1,5 @@ +package io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer; + +public interface Callback1 { + void call(T t); +} diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/ExoPlayerInteractor.java b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/ExoPlayerInteractor.java new file mode 100644 index 00000000..c58a3e26 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/ExoPlayerInteractor.java @@ -0,0 +1,62 @@ +package io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer; + +import androidx.annotation.NonNull; + +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Player.EventListener; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; + +import java.util.concurrent.atomic.AtomicBoolean; + +import io.streamroot.lumen.delivery.client.core.LumenPlayerInteractorBase; +import io.streamroot.lumen.delivery.client.core.LumenVideoPlaybackState; + +public final class ExoPlayerInteractor extends LumenPlayerInteractorBase implements EventListener { + + private AtomicBoolean listening = new AtomicBoolean(false); + + public ExoPlayerInteractor() {} + public ExoPlayerInteractor(@NonNull ExoPlayer exoPlayer) { setPlayer(exoPlayer); } + + public void setPlayer(@NonNull ExoPlayer exoPlayer) { + if (listening.compareAndSet(false, true)) { + exoPlayer.addListener(this); + } + } + + @Override + public void onSeekProcessed() { + super.playerStateChange(LumenVideoPlaybackState.SEEKING); + } + + @Override + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { + super.playerTrackSwitch(); + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + super.playerError(); + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + switch(playbackState) { + case Player.STATE_IDLE: + super.playerStateChange(LumenVideoPlaybackState.IDLE); + break; + case Player.STATE_BUFFERING: + super.playerStateChange(LumenVideoPlaybackState.REBUFFERING); + break; + case Player.STATE_READY: + super.playerStateChange(playWhenReady ? LumenVideoPlaybackState.PLAYING : LumenVideoPlaybackState.PAUSED); + break; + case Player.STATE_ENDED: + super.playerStateChange(LumenVideoPlaybackState.ENDED); + break; + } + } +} \ No newline at end of file diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/MainActivity.java b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/MainActivity.java new file mode 100644 index 00000000..6fe5719a --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/MainActivity.java @@ -0,0 +1,36 @@ +package io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer; + +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + findViewById(R.id.launchButton).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final String stream = ((TextView)findViewById(R.id.streamEditText)).getText().toString(); + if (TextUtils.isEmpty(stream.trim())) return; + + final Intent i = PlayerActivity.makeIntent(MainActivity.this, + new PlayerActivity.PlayerActivityArgs( + ((TextView)findViewById(R.id.dcKeyET)).getText().toString(), + stream, + ((TextView)findViewById(R.id.orchPropET)).getText().toString() + ) + ).addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + } + }); + } +} diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/PlayerActivity.java b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/PlayerActivity.java new file mode 100644 index 00000000..a9c6ace4 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/PlayerActivity.java @@ -0,0 +1,363 @@ +package io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; +import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; +import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; +import com.google.android.exoplayer2.source.LoopingMediaSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.dash.DashMediaSource; +import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.ui.PlayerView; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.util.Util; +import com.yospace.android.hls.analytic.AnalyticEventListener; +import com.yospace.android.hls.analytic.Session; +import com.yospace.android.hls.analytic.advert.AdBreak; +import com.yospace.android.hls.analytic.advert.Advert; +import com.yospace.android.xml.VastPayload; +import com.yospace.android.xml.VmapPayload; + +import org.jetbrains.annotations.Nullable; + +import io.streamroot.lumen.delivery.client.core.LumenDeliveryClient; +import io.streamroot.lumen.delivery.client.core.LumenLogLevel; +import io.streamroot.lumen.delivery.client.core.LumenOptionalOrchestratorBuilder; +import io.streamroot.lumen.delivery.client.core.LumenPlayerInteractorBase; +import io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer.common.PlayerAdapter; +import io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer.common.PlayerAdapterLive; +import io.streamroot.lumen.delivery.client.utils.LumenStatsView; +import kotlin.Unit; +import kotlin.jvm.functions.Function1; + +public class PlayerActivity extends AppCompatActivity implements Player.EventListener, AnalyticEventListener { + + private static final Session.PlaybackMode YOSPACE_MODE = Session.PlaybackMode.LIVE; + + public static final class PlayerActivityArgs { + @Nullable final String dcKey; + @Nullable final String url; + @Nullable final String orchProperty; + public PlayerActivityArgs(@Nullable String dcKey, @Nullable String url, @Nullable String orchProperty) { + this.dcKey = dcKey; + this.url = url; + this.orchProperty = orchProperty; + } + } + + private static final String ARG_DC_KEY = "dcKey"; + private static final String ARG_STREAM_URL = "streamUrl"; + private static final String ARG_ORCH_PROP = "orchestratorProperty"; + + public static Intent makeIntent(Context ctx, PlayerActivityArgs args) { + return new Intent(ctx, PlayerActivity.class) + .putExtra(ARG_DC_KEY, args.dcKey) + .putExtra(ARG_STREAM_URL, args.url) + .putExtra(ARG_ORCH_PROP, args.orchProperty); + } + + public static PlayerActivityArgs extractArgs(Intent i) { + return new PlayerActivityArgs( + i.getStringExtra(ARG_DC_KEY), + i.getStringExtra(ARG_STREAM_URL), + i.getStringExtra(ARG_ORCH_PROP)); + } + + @Nullable private PlayerView exoPlayerView = null; + @Nullable private LumenStatsView dcStatsView = null; + + @Nullable private String mDCKey = null; + // @Nullable private String mStreamUrl = null; + @Nullable private String mOrchProperty = null; + + @Nullable private ExoPlayer player = null; + + @Nullable private LumenDeliveryClient deliveryClient = null; + + @NonNull private YospaceModule.YospaceBridgeStruct yoBridge = new YospaceModule.YospaceBridgeStruct(); + @Nullable private Handler mMainHandler = null; + + @Override + protected void onCreate(@androidx.annotation.Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_player); + + mMainHandler = new Handler(); // created with main looper + final PlayerActivityArgs args = extractArgs(getIntent()); + // mStreamUrl = args.url; + { + final String tmpOP = args.orchProperty; + mOrchProperty = (tmpOP != null && !tmpOP.trim().isEmpty()) ? tmpOP : null; + } + { + final String tmp = args.dcKey; + mDCKey = (tmp != null && !tmp.trim().isEmpty()) ? tmp : null; + } + + exoPlayerView = findViewById(R.id.exoplayerView); + dcStatsView = findViewById(R.id.dcStatsView); + } + + @Override + protected void onStart() { + super.onStart(); + + if (Util.SDK_INT > 23) { + initPlayer(); + } + } + + @Override + protected void onPause() { + super.onPause(); + + if (Util.SDK_INT <= 23) { + releasePlayer(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (Util.SDK_INT <= 23 || player == null) { + initPlayer(); + } + } + + @Override + protected void onStop() { + if (Util.SDK_INT > 23) { + releasePlayer(); + } + + super.onStop(); + } + + private void initPlayer() { + if (player == null) { + YospaceModule.createAdapterAndSession(this, this, YOSPACE_MODE, new YospaceModule.YospaceModuleCallback() { + @Override + public void onSessionAvailable(Session session) { + yoBridge.mSession = session; + } + + @Override + public void onFinalUrlReady(PlayerAdapter adapter, String finalYospaceUrl) { + Log.v("PlayerActivity", "finalYospace url => " + finalYospaceUrl); + yoBridge.mAdapter = adapter; + yoBridge.mFinalYospaceUrl = finalYospaceUrl; + + // Build the player + final SimpleExoPlayer newPlayer = new SimpleExoPlayer.Builder(getApplicationContext()).build(); + + newPlayer.setPlayWhenReady(true); + + newPlayer.addListener(PlayerActivity.this); + newPlayer.addListener(adapter); + adapter.setVideoPlayer(newPlayer); + + if (adapter instanceof PlayerAdapterLive) { + newPlayer.addMetadataOutput((PlayerAdapterLive) adapter); + } + + // Include streamroot in the middle + final ExoPlayerInteractor interactor = new ExoPlayerInteractor(newPlayer); + + final LumenDeliveryClient dc = initDeliveryClient(interactor, finalYospaceUrl); + deliveryClient = dc; + dc.addStateStatsListener(dcStatsView); + dcStatsView.showStats(); + dc.start(); + + final Uri uri = Uri.parse(dc.localUrl()); + final MediaSource ms = buildMediaSource(uri, YOSPACE_MODE == Session.PlaybackMode.NONLINEARSTARTOVER ? true : null); + ms.addEventListener(mMainHandler, adapter); + + newPlayer.prepare(new LoopingMediaSource(ms), true, false); + + player = newPlayer; + exoPlayerView.setPlayer(newPlayer); + } + }); + } + } + + private void releasePlayer() { + if (player != null) { + player.release(); + player = null; + } + stopDeliveryClient(); + + if (yoBridge.mSession != null) + { + yoBridge.mSession.shutdown(); + yoBridge.mSession = null; + } + } + + @SuppressLint("SwitchIntDef") + /** + * Quick solution but isHls should be replaced with an enum + */ + private MediaSource buildMediaSource(Uri uri, Boolean isHls) { + final DefaultHttpDataSourceFactory defaultDataSourceFactory = new DefaultHttpDataSourceFactory( + Util.getUserAgent(getApplicationContext(), "StreamrootQA"), + DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, + true + ); + + int type = Util.inferContentType(uri); + if ((isHls != null && isHls) || type == C.TYPE_HLS) { + return new HlsMediaSource.Factory(defaultDataSourceFactory) + //.setDrmSessionManager() + .createMediaSource(uri); + } else if (type == C.TYPE_DASH) { + return new DashMediaSource.Factory( + new DefaultDashChunkSource.Factory( + defaultDataSourceFactory + ), defaultDataSourceFactory + ) + //.setDrmSessionManager() + .createMediaSource(uri); + } else { + throw new IllegalStateException("Unsupported type for url: $uri"); + } + } + + private LumenDeliveryClient initDeliveryClient( + final LumenPlayerInteractorBase playerInteractor, + final String playerUrl + ) { + return LumenDeliveryClient.orchestratorBuilder(getApplicationContext()) + .playerInteractor(playerInteractor) + .options(new Function1() { + @Override + public Unit invoke(LumenOptionalOrchestratorBuilder o) { + o.logLevel(LumenLogLevel.TRACE); + if (mDCKey != null) o.deliveryClientKey(mDCKey); + if (mOrchProperty != null) o.orchestratorProperty(mOrchProperty); + + return null; + } + }).build(playerUrl); + } + + private void stopDeliveryClient() { + if (deliveryClient != null) { + deliveryClient.terminate(); + deliveryClient = null; + } + } + + /** + * Utils + */ + + private void showToast(String message) { + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); + } + + /** + * Player EventListener + */ + + @Override + public void onPlayerError(ExoPlaybackException error) { + @Nullable String errorString = null; + if (error.type == ExoPlaybackException.TYPE_RENDERER) { + final Exception cause = error.getRendererException(); + if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { + final MediaCodecRenderer.DecoderInitializationException castedCause = + (MediaCodecRenderer.DecoderInitializationException) cause; + // Special case for decoder initialization failures. + final MediaCodecInfo codecInfo = castedCause.codecInfo; + + if (codecInfo != null) { + errorString = getString( + R.string.error_instantiating_decoder, + codecInfo.name + ); + } else if (castedCause.getCause() instanceof MediaCodecUtil.DecoderQueryException) { + errorString = getString(R.string.error_querying_decoders); + } else if (castedCause.secureDecoderRequired) { + errorString = getString( + R.string.error_no_secure_decoder, + castedCause.mimeType); + } else { + errorString = getString(R.string.error_no_decoder, castedCause.mimeType); + } + } + } + + if (errorString != null) { + showToast(errorString); + } + } + + /////////////////////////////////////// + // AnalyticEventListener implementation + + @Override + public void onAdvertBreakEnd(AdBreak adBreak) { + showToast("Adbreak end"); + } + + @Override + public void onAdvertBreakStart(AdBreak adBreak) { + showToast("Adbreak start"); + } + + @Override + public void onAdvertEnd(final Advert advert) + { + showToast("Adbreak end"); + } + + @Override + public void onAdvertStart(final Advert advert) + { + showToast(advert.getId() + ":0"); + } + + @Override + public void onTimelineUpdateReceived(VmapPayload vmap) { /* do nothing */ } + + @Override + public void onVastReceived(VastPayload vast) { + showToast("VAST received"); + } + + @Override + public void onTrackingUrlCalled(final Advert advert, final String type, String url) + { + String quartile = type.equals("firstQuartile") ? ":1" : null; + quartile = type.equals("midpoint") ? ":2" : quartile; + quartile = type.equals("thirdQuartile") ? ":3" : quartile; + + if (quartile != null) { + showToast(advert.getId() + quartile); + } + } +} diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/SRApplication.java b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/SRApplication.java new file mode 100644 index 00000000..e934d815 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/SRApplication.java @@ -0,0 +1,13 @@ +package io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer; + +import androidx.multidex.MultiDexApplication; + +import io.streamroot.lumen.delivery.client.core.LumenDeliveryClient; + +public final class SRApplication extends MultiDexApplication { + @Override + public void onCreate() { + super.onCreate(); + LumenDeliveryClient.initializeApp(this); + } +} diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/YospaceModule.java b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/YospaceModule.java new file mode 100644 index 00000000..a8ff9df1 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/YospaceModule.java @@ -0,0 +1,341 @@ +package io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer; + +import android.app.Activity; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer.common.Constant; +import io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer.common.PlayerAdapter; +import io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer.common.PlayerAdapterLive; +import io.streamroot.lumen.delivery.client.samples.orchestrator.exoplayer.common.PlayerPolicyImpl; + +import com.yospace.android.hls.analytic.AnalyticEventListener; +import com.yospace.android.hls.analytic.Session; +import com.yospace.android.hls.analytic.SessionFactory; +import com.yospace.android.hls.analytic.SessionLive; +import com.yospace.android.hls.analytic.SessionLivePause; +import com.yospace.android.hls.analytic.SessionNonLinear; +import com.yospace.android.hls.analytic.SessionNonLinearStartOver; +import com.yospace.util.YoLog; +import com.yospace.util.event.Event; +import com.yospace.util.event.EventListener; + +import static com.yospace.android.hls.analytic.Constant.NO_LIVEPAUSE; + +public class YospaceModule { + + public interface YospaceModuleCallback { + void onSessionAvailable(Session session); + void onFinalUrlReady(PlayerAdapter adapter, String finalYospaceUrl); + } + + public static class YospaceBridgeStruct { + public @Nullable Session mSession = null; + public @Nullable String mFinalYospaceUrl = null; + public @Nullable PlayerAdapter mAdapter = null; + } + + public static void createAdapterAndSession(@NonNull final Activity a, + @NonNull final AnalyticEventListener analyticsObserver, + @NonNull final Session.PlaybackMode mode, + @NonNull final YospaceModuleCallback cb) + { + switch (mode) { + case LIVE: + PlayerAdapterLive adapterLive = new PlayerAdapterLive(a, null); + createLive(a, analyticsObserver, Constant.VIDEO_URL_LIVE, adapterLive, cb); + break; + case LIVEPAUSE: + adapterLive = new PlayerAdapterLive(a, null); + createLivePause(a, analyticsObserver, Constant.VIDEO_URL_LIVE_PAUSE, adapterLive, cb); + break; + case NONLINEAR: + PlayerAdapter adapter = new PlayerAdapter(a, null); + createNonLinear(a, analyticsObserver, Constant.VIDEO_URL_VOD, adapter, cb); + break; + case NONLINEARSTARTOVER: + adapter = new PlayerAdapter(a, null); + createNonLinearStartOver(a, analyticsObserver, Constant.VIDEO_URL_NLSO, adapter, cb); + break; + } + } + + // Session url can be returned + private static void createLive(final Activity a, + final AnalyticEventListener analyticsObserver, + final String url, + final PlayerAdapterLive adapter, + @NonNull final YospaceModuleCallback cb) { + Log.i(Constant.getLogTag(), "PlayerLive.initialiseYospace - Initialise Yospace analytics"); + + Session.SessionProperties properties = new Session.SessionProperties(url).userAgent(Constant.USER_AGENT); + + properties.addDebugFlags(YoLog.DEBUG_ALL); + + final SessionFactory sf = SessionFactory.create(new EventListener() { + + /** + * Callback made by SessionLive once it has initialised a session on the Yospace CSM + */ + @Override + public void handle(Event event) { + + // Retrieve the initialised session + final SessionLive mSession = (SessionLive) event.getPayload(); + + switch (mSession.getState()) { + + case INITIALISED: + Log.i(Constant.getLogTag(), "PlayerLive.initialiseYospace - Yospace analytics session initialised"); + + adapter.setSession(mSession); + + // Instantiate a LogAnalyticEventListener to make Analytic events visible in the log + mSession.addAnalyticListener(analyticsObserver); + mSession.setPlayerPolicy(new PlayerPolicyImpl()); + + a.runOnUiThread(new Runnable() { + @Override + public void run() { + cb.onSessionAvailable(mSession); + } + }); + + break; + + case NO_ANALYTICS: + Log.i(Constant.getLogTag(), + "PlayerLive.initialiseYospace - No analytics session created, result code: " + + mSession.getResultCode()); + break; + + case NOT_INITIALISED: + Log.e(Constant.getLogTag(), "PlayerLive.initialiseYospace - Failed to initialise analytics session, result code: " + + mSession.getResultCode()); + break; + + default: + break; + } + } + }, properties, Session.PlaybackMode.LIVE); + + a.runOnUiThread(new Runnable() { + @Override + public void run() { + cb.onFinalUrlReady(adapter, sf.getPlayerUrl()); + } + }); + } + + // Session url can be returned + private static void createLivePause(final Activity a, + final AnalyticEventListener analyticsObserver, + final String url, + final PlayerAdapterLive adapter, + @NonNull final YospaceModuleCallback cb) { + Log.i(Constant.getLogTag(), "PlayerLivePause.initialiseYospace - Initialise Yospace analytics"); + + Session.SessionProperties properties = new Session.SessionProperties(url).userAgent(Constant.USER_AGENT); + + properties.addDebugFlags(YoLog.DEBUG_PARSING | YoLog.DEBUG_POLLING | YoLog.DEBUG_HEARTBEAT_STATE); + + final SessionFactory sf = SessionFactory.create(new EventListener() { + + /** + * Callback made by SessionLivePause once it has initialised a session on the Yospace VoD-e service + */ + @Override + public void handle(Event event) { + + // Retrieve the session + final Session session = event.getPayload(); + + switch (session.getState()) { + + case INITIALISED: + if (session.getResultCode() == NO_LIVEPAUSE) + { + Log.i(Constant.getLogTag(), + "PlayerLivePause.initialiseYospace - Video URL is not configured as a LivePause stream"); + + // In this case playback reverts to DAI Live and the session object is of type SessionLive. + // A customer application would handle this case in the same manner as shown in the + // PlayerLive.java sample file. + // Note that since the stream is Live, a timeline is unavailable in this case. + + a.runOnUiThread(new Runnable() { + @Override + public void run() { + cb.onSessionAvailable(session); + } + }); + } + else + { + Log.i(Constant.getLogTag(), "PlayerLivePause.initialiseYospace - Yospace analytics session initialised"); + + final SessionLivePause mSession = (SessionLivePause) event.getPayload(); + adapter.setSession(mSession); + + // Instantiate a UIAnalyticListener to make Analytic events visible in the log and to update the UI + mSession.addAnalyticListener(analyticsObserver); + mSession.setPlayerPolicy(new PlayerPolicyImpl()); + + a.runOnUiThread(new Runnable() { + @Override + public void run() { + cb.onSessionAvailable(mSession); + } + }); + } + break; + + case NO_ANALYTICS: + Log.i(Constant.getLogTag(), + "PlayerLivePause.initialiseYospace - Video URL does not refer to a Yospace stream, no analytics session created"); + break; + + case NOT_INITIALISED: + Log.e(Constant.getLogTag(), "PlayerLivePause.initialiseYospace - Failed to initialise analytics session"); + break; + + default: + break; + } + + } + + }, properties, Session.PlaybackMode.LIVEPAUSE); + + a.runOnUiThread(new Runnable() { + @Override + public void run() { + cb.onFinalUrlReady(adapter, sf.getPlayerUrl()); + } + }); + } + + private static void createNonLinear(final Activity a, + final AnalyticEventListener analyticsObserver, + final String url, + final PlayerAdapter adapter, + @NonNull final YospaceModuleCallback cb) { + Log.i(Constant.getLogTag(), "PlayerNonLinear.initialiseYospace - Initialise Yospace analytics"); + + Session.SessionProperties properties = new Session.SessionProperties(url).userAgent(Constant.USER_AGENT); + + properties.addDebugFlags(YoLog.DEBUG_PARSING | YoLog.DEBUG_POLLING | YoLog.DEBUG_HEARTBEAT_STATE); + + SessionNonLinear.create(new EventListener() { + + /** + * Callback made by SessionNonLinear once it has initialised a session on the Yospace VoD-e service + */ + @Override + public void handle(Event event) { + + // Retrieve the initialised session + final SessionNonLinear mSession = (SessionNonLinear) event.getPayload(); + + switch (mSession.getState()) { + + case INITIALISED: + Log.i(Constant.getLogTag(), "PlayerNonLinear.initialiseYospace - Yospace analytics session initialised"); + + adapter.setSession(mSession); + + // Instantiate a LogAnalyticEventListener to make Analytic events visible in the log + mSession.addAnalyticListener(analyticsObserver); + mSession.setPlayerPolicy(new PlayerPolicyImpl()); + + a.runOnUiThread(new Runnable() { + @Override + public void run() { + cb.onSessionAvailable(mSession); + cb.onFinalUrlReady(adapter, mSession.getPlayerUrl()); + } + }); + break; + + case NO_ANALYTICS: + Log.i(Constant.getLogTag(), + "PlayerNonLinear.initialiseYospace - Video URL does not refer to a Yospace stream, no analytics session created"); + break; + + case NOT_INITIALISED: + Log.e(Constant.getLogTag(), "PlayerNonLinear.initialiseYospace - Failed to initialise analytics session"); + break; + + default: + break; + } + + } + + }, properties); + } + + private static void createNonLinearStartOver(final Activity a, + final AnalyticEventListener analyticsObserver, + final String url, + final PlayerAdapter adapter, + @NonNull final YospaceModuleCallback cb) { + Log.i(Constant.getLogTag(), "PlayerNonLinearStartOver.initialiseYospace - Initialise Yospace analytics"); + + Session.SessionProperties properties = new Session.SessionProperties(url).userAgent(Constant.USER_AGENT); + + properties.addDebugFlags(YoLog.DEBUG_PARSING | YoLog.DEBUG_POLLING | YoLog.DEBUG_HEARTBEAT_STATE); + + SessionNonLinearStartOver.create(new EventListener() { + + /** + * Callback made by SessionNonLinear once it has initialised a session on the Yospace VoD-e service + */ + @Override + public void handle(Event event) { + + // Retrieve the initialised session + final SessionNonLinearStartOver mSession = (SessionNonLinearStartOver) event.getPayload(); + + switch (mSession.getState()) { + + case INITIALISED: + Log.i(Constant.getLogTag(), "PlayerNonLinearStartOver.initYo - Yospace analytics session initialised"); + + adapter.setSession(mSession); + + // Instantiate a LogAnalyticEventListener to make Analytic events visible in the log + mSession.addAnalyticListener(analyticsObserver); + mSession.setPlayerPolicy(new PlayerPolicyImpl()); + + a.runOnUiThread(new Runnable() { + @Override + public void run() { + cb.onSessionAvailable(mSession); + cb.onFinalUrlReady(adapter, mSession.getPlayerUrl()); + } + }); + + break; + + case NO_ANALYTICS: + Log.i(Constant.getLogTag(), + "PlayerNonLinearStartOver.initYo - Video URL does not refer to a Yospace stream, no analytics session created"); + break; + + case NOT_INITIALISED: + Log.e(Constant.getLogTag(), "PlayerNonLinearStartOver.initYo - Failed to initialise analytics session"); + break; + + default: + break; + } + + } + + }, properties); + } +} diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/common b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/common new file mode 120000 index 00000000..54225569 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/java/io/streamroot/lumen/delivery/client/samples/orchestrator/exoplayer/common @@ -0,0 +1 @@ +../../../../../../../../../../../../ssp/exoplayer-yospace/common \ No newline at end of file diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..c7bd21db --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable/ic_launcher_background.xml b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..d5fccc53 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable/streamroot_logo.png b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable/streamroot_logo.png new file mode 100644 index 00000000..33cc7499 Binary files /dev/null and b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/drawable/streamroot_logo.png differ diff --git a/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/layout/activity_main.xml b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..a2164e27 --- /dev/null +++ b/orchestrator/android/ExoPlayer-Yospace-Java/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +