diff --git a/cast_receiver_app/BUILD b/cast_receiver_app/BUILD
deleted file mode 100644
index 2bd0526cdd..0000000000
--- a/cast_receiver_app/BUILD
+++ /dev/null
@@ -1,310 +0,0 @@
-# Copyright (C) 2019 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.
-
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_binary")
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_test")
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_css_library")
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_css_binary")
-
-licenses(["notice"]) # Apache 2.0
-
-# The Shaka player library - 2.5.0-beta2 (needs to be cloned from Github).
-closure_js_library(
- name = "shaka_player_library",
- srcs = glob(
- [
- "external-js/shaka-player/lib/**/*.js",
- "external-js/shaka-player/externs/**/*.js",
- ],
- exclude = [
- "external-js/shaka-player/lib/debug/asserts.js",
- "external-js/shaka-player/externs/mediakeys.js",
- "external-js/shaka-player/externs/networkinformation.js",
- "external-js/shaka-player/externs/vtt_region.js",
- ],
- ),
- suppress = [
- "strictMissingRequire",
- "missingSourcesWarnings",
- "analyzerChecks",
- "strictCheckTypes",
- "checkTypes",
- ],
- deps = [
- "@io_bazel_rules_closure//closure/library",
- ],
-)
-
-# The plain player not depending on the cast library.
-closure_js_library(
- name = "player_lib",
- srcs = [
- "externs/protocol.js",
- "src/configuration_factory.js",
- "src/constants.js",
- "src/playback_info_view.js",
- "src/player.js",
- "src/timeout.js",
- "src/util.js",
- ],
- suppress = [
- "missingSourcesWarnings",
- "analyzerChecks",
- "strictCheckTypes",
- ],
- deps = [
- ":shaka_player_library",
- "@io_bazel_rules_closure//closure/library",
- ],
-)
-
-# A debug app to test the player with a desktop browser.
-closure_js_library(
- name = "app_desktop_lib",
- srcs = [
- "app-desktop/src/main.js",
- "app-desktop/src/player_controls.js",
- "app-desktop/src/samples.js",
- "externs/shaka.js",
- ],
- suppress = [
- "reportUnknownTypes",
- "strictCheckTypes",
- ],
- deps = [
- ":player_lib",
- ":shaka_player_library",
- "@io_bazel_rules_closure//closure/library",
- ],
-)
-
-# Includes the javascript files of the cast receiver app.
-closure_js_library(
- name = "app_lib",
- srcs = [
- "app/src/main.js",
- "app/src/message_dispatcher.js",
- "app/src/receiver.js",
- "app/src/validation.js",
- "externs/cast.js",
- "externs/shaka.js",
- ],
- suppress = [
- "missingSourcesWarnings",
- "analyzerChecks",
- "strictCheckTypes",
- ],
- deps = [
- ":player_lib",
- ":shaka_player_library",
- "@io_bazel_rules_closure//closure/library",
- ],
-)
-
-# Test utils like mocks.
-closure_js_library(
- name = "test_util_lib",
- testonly = 1,
- srcs = [
- "externs/protocol.js",
- "test/externs.js",
- "test/mocks.js",
- "test/util.js",
- ],
- suppress = [
- "checkTypes",
- "strictCheckTypes",
- "reportUnknownTypes",
- "accessControls",
- "analyzerChecks",
- "missingSourcesWarnings",
- ],
- deps = [
- ":shaka_player_library",
- "@io_bazel_rules_closure//closure/library",
- "@io_bazel_rules_closure//closure/library/testing:jsunit",
- ],
-)
-
-# Unit test for the player.
-closure_js_test(
- name = "player_tests",
- srcs = glob([
- "test/player_test.js",
- ]),
- entry_points = [
- "exoplayer.cast.test",
- ],
- suppress = [
- "checkTypes",
- "strictCheckTypes",
- "reportUnknownTypes",
- "accessControls",
- "analyzerChecks",
- "missingSourcesWarnings",
- ],
- deps = [
- ":app_lib",
- ":player_lib",
- ":test_util_lib",
- "@io_bazel_rules_closure//closure/library/testing:asserts",
- "@io_bazel_rules_closure//closure/library/testing:jsunit",
- "@io_bazel_rules_closure//closure/library/testing:testsuite",
- ],
-)
-
-# Unit test for the queue in the player.
-closure_js_test(
- name = "queue_tests",
- srcs = glob([
- "test/queue_test.js",
- ]),
- entry_points = [
- "exoplayer.cast.test.queue",
- ],
- suppress = [
- "checkTypes",
- "strictCheckTypes",
- "reportUnknownTypes",
- "accessControls",
- "analyzerChecks",
- "missingSourcesWarnings",
- ],
- deps = [
- ":app_lib",
- ":player_lib",
- ":test_util_lib",
- "@io_bazel_rules_closure//closure/library/testing:asserts",
- "@io_bazel_rules_closure//closure/library/testing:jsunit",
- "@io_bazel_rules_closure//closure/library/testing:testsuite",
- ],
-)
-
-# Unit test for the receiver.
-closure_js_test(
- name = "receiver_tests",
- srcs = glob([
- "test/receiver_test.js",
- ]),
- entry_points = [
- "exoplayer.cast.test.receiver",
- ],
- suppress = [
- "checkTypes",
- "strictCheckTypes",
- "reportUnknownTypes",
- "accessControls",
- "analyzerChecks",
- "missingSourcesWarnings",
- ],
- deps = [
- ":app_lib",
- ":player_lib",
- ":test_util_lib",
- "@io_bazel_rules_closure//closure/library/testing:asserts",
- "@io_bazel_rules_closure//closure/library/testing:jsunit",
- "@io_bazel_rules_closure//closure/library/testing:testsuite",
- ],
-)
-
-# Unit test for the validations.
-closure_js_test(
- name = "validation_tests",
- srcs = [
- "test/validation_test.js",
- ],
- entry_points = [
- "exoplayer.cast.test.validation",
- ],
- suppress = [
- "checkTypes",
- "strictCheckTypes",
- "reportUnknownTypes",
- "accessControls",
- "analyzerChecks",
- "missingSourcesWarnings",
- ],
- deps = [
- ":app_lib",
- ":player_lib",
- ":test_util_lib",
- "@io_bazel_rules_closure//closure/library/testing:asserts",
- "@io_bazel_rules_closure//closure/library/testing:jsunit",
- "@io_bazel_rules_closure//closure/library/testing:testsuite",
- ],
-)
-
-# The receiver app as a compiled binary.
-closure_js_binary(
- name = "app",
- entry_points = [
- "exoplayer.cast.app",
- "shaka.dash.DashParser",
- "shaka.hls.HlsParser",
- "shaka.abr.SimpleAbrManager",
- "shaka.net.HttpFetchPlugin",
- "shaka.net.HttpXHRPlugin",
- "shaka.media.AdaptationSetCriteria",
- ],
- deps = [":app_lib"],
-)
-
-# The debug app for the player as a compiled binary.
-closure_js_binary(
- name = "app_desktop",
- entry_points = [
- "exoplayer.cast.debug",
- "exoplayer.cast.samples",
- "shaka.dash.DashParser",
- "shaka.hls.HlsParser",
- "shaka.abr.SimpleAbrManager",
- "shaka.net.HttpFetchPlugin",
- "shaka.net.HttpXHRPlugin",
- "shaka.media.AdaptationSetCriteria",
- ],
- deps = [":app_desktop_lib"],
-)
-
-# Defines the css style of the receiver app.
-closure_css_library(
- name = "app_styles_lib",
- srcs = [
- "app/html/index.css",
- "app/html/playback_info_view.css",
- ],
-)
-
-# Defines the css styles of the debug app.
-closure_css_library(
- name = "app_desktop_styles_lib",
- srcs = [
- "app-desktop/html/index.css",
- "app/html/playback_info_view.css",
- ],
-)
-
-# Compiles the css styles of the receiver app.
-closure_css_binary(
- name = "app_styles",
- renaming = False,
- deps = ["app_styles_lib"],
-)
-
-# Compiles the css styles of the debug app.
-closure_css_binary(
- name = "app_desktop_styles",
- renaming = False,
- deps = ["app_desktop_styles_lib"],
-)
diff --git a/cast_receiver_app/README.md b/cast_receiver_app/README.md
deleted file mode 100644
index 6504cb4f94..0000000000
--- a/cast_receiver_app/README.md
+++ /dev/null
@@ -1,72 +0,0 @@
-# ExoPlayer cast receiver #
-
-An HTML/JavaScript app which runs within a Google cast device and can be loaded
-and controller by an Android app which uses the ExoPlayer cast extension
-(https://github.com/google/ExoPlayer/tree/release-v2/extensions/cast).
-
-# Build the app #
-
-You can build and deploy the app to your web server and register the url as your
-cast receiver app (see: https://developers.google.com/cast/docs/registration).
-
-Building the app compiles JavaScript and CSS files. Dead JavaScript code of the
-app itself and their dependencies (like ShakaPlayer) is removed and the
-remaining code is minimized.
-
-## Prerequisites ##
-
-1. Install the most recent bazel release (https://bazel.build/) which is at
- least 0.22.0.
-
-From within the root of the exo_receiver_app project do the following steps:
-
-2. Clone shaka from GitHub into the directory external-js/shaka-player:
-```
-# git clone https://github.com/google/shaka-player.git \
- external-js/shaka-player
-```
-
-## 1. Customize html page and css (optional) ##
-
-(Optional) Edit index.html. **Make sure you do not change the id of the video
-element**.
-(Optional) Customize main.css.
-
-## 2. Build javascript and css files ##
-```
-# bazel build ...
-```
-## 3. Assemble the receiver app ##
-```
-# WEB_DEPLOY_DIR=www
-# mkdir ${WEB_DEPLOY_DIR}
-# cp bazel-bin/exo_receiver_app.js ${WEB_DEPLOY_DIR}
-# cp bazel-bin/exo_receiver_styles_bin.css ${WEB_DEPLOY_DIR}
-# cp html/index.html ${WEB_DEPLOY_DIR}
-```
-
-Deploy the content of ${WEB_DEPLOY_DIR} to your web server.
-
-## 4. Assemble the debug app (optional) ##
-
-Debugging the player in a cast device is a little bit cumbersome compared to
-debugging in a desktop browser. For this reason there is a debug app which
-contains the player parts which are not depending on the cast library in a
-traditional HTML app which can be run in a desktop browser.
-
-```
-# WEB_DEPLOY_DIR=www
-# mkdir ${WEB_DEPLOY_DIR}
-# cp bazel-bin/debug_app.js ${WEB_DEPLOY_DIR}
-# cp bazel-bin/debug_styles_bin.css ${WEB_DEPLOY_DIR}
-# cp html/player.html ${WEB_DEPLOY_DIR}
-```
-
-Deploy the content of ${WEB_DEPLOY_DIR} to your web server.
-
-# Unit test
-
-Unit tests can be run by the command
-```
-# bazel test ...
-```
diff --git a/cast_receiver_app/WORKSPACE b/cast_receiver_app/WORKSPACE
deleted file mode 100644
index e6be3b9026..0000000000
--- a/cast_receiver_app/WORKSPACE
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2019 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.
-
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-
-http_archive(
- name = "com_google_protobuf",
- sha256 = "73fdad358857e120fd0fa19e071a96e15c0f23bb25f85d3f7009abfd4f264a2a",
- strip_prefix = "protobuf-3.6.1.3",
- urls = ["https://github.com/google/protobuf/archive/v3.6.1.3.tar.gz"],
-)
-
-http_archive(
- name = "io_bazel_rules_closure",
- sha256 = "b29a8bc2cb10513c864cb1084d6f38613ef14a143797cea0af0f91cd385f5e8c",
- strip_prefix = "rules_closure-0.8.0",
- urls = [
- "https://mirror.bazel.build/github.com/bazelbuild/rules_closure/archive/0.8.0.tar.gz",
- "https://github.com/bazelbuild/rules_closure/archive/0.8.0.tar.gz",
- ],
-)
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories")
-
-closure_repositories(
- omit_com_google_protobuf = True,
-)
-
diff --git a/cast_receiver_app/app-desktop/html/index.css b/cast_receiver_app/app-desktop/html/index.css
deleted file mode 100644
index ff77e1cbfa..0000000000
--- a/cast_receiver_app/app-desktop/html/index.css
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-html, body, section, video, div, span, ul, li {
- border: 0;
- box-sizing: border-box;
- margin: 0;
- padding: 0;
-}
-body, html {
- height: 100%;
- overflow: auto;
- background-color: #333;
- color: #eeeeee;
- font-family: Roboto, Arial, sans-serif;
-}
-body {
- padding-top: 24px;
-}
-.exo_controls {
- list-style: none;
- padding: 0;
- white-space: nowrap;
- margin-top: 12px;
-}
-.exo_controls > li {
- display: inline-block;
- width: 72px;
-}
-.exo_controls > .large {
- width: 140px;
-}
-/* an action element to add or remove a media item */
-.action {
- margin: 4px auto;
- max-width: 640px;
-}
-.action.prepared {
- background-color: #AA0000;
-}
-/** marks whether a given media item is in the queue */
-.queue-marker {
- background-color: #AA0000;
- border-radius: 50%;
- border: 1px solid #ffc0c0;
- display: none;
- float: right;
- height: 1em;
- margin-top: 1px;
- width: 1em;
-}
-.action[data-uuid] .queue-marker {
- display: inline-block;
-}
-.action.prepared .queue-marker {
- background-color: #fff900;
-}
-.playing .action.prepared .queue-marker {
- animation-name: spin;
- animation-iteration-count: infinite;
- animation-duration: 1.6s;
-}
-/* A simple button. */
-.button {
- background-color: #45484d;
- border: 1px solid #495267;
- border-radius: 3px;
- color: #FFFFFF;
- cursor: pointer;
- font-size: 12px;
- font-weight: bold;
- padding: 10px 10px 10px 10px;
- text-decoration: none;
- text-shadow: -1px -1px 0 rgba(0,0,0,0.3);
- -webkit-user-select: none;
-}
-.button:hover {
- border: 1px solid #363d4c;
- background-color: #2d2f32;
- background-image: linear-gradient(to bottom, #2d2f32, #1a1a1a);
-}
-.ribbon {
- background-color: #003a5dc2;
- box-shadow: 2px 2px 4px #000;
- left: -60px;
- height: 3.3em;
- padding-top: 7px;
- position: absolute;
- text-align: center;
- top: 27px;
- transform: rotateZ(-45deg);
- width: 220px;
- border: 1px dashed #cacaca;
- outline-color: #003a5dc2;
- outline-width: 2px;
- outline-style: solid;
-}
-.ribbon a {
- color: white;
- text-decoration: none;
- -webkit-user-select: none;
-}
-#button_prepare {
- left: 0;
- position: absolute;
-}
-#button_stop {
- position: absolute;
- right: 0;
-}
-#exo_demo_view {
- height: 360px;
- margin: auto;
- overflow: hidden;
- position: relative;
- width: 640px;
-}
-#video {
- background-color: #000;
- border-radius: 8px;
- height: 100%;
- margin-bottom: auto;
- margin-top: auto;
- width: 100%;
-}
-#exo_controls {
- display: none;
- margin: auto;
- position: relative;
- text-align: center;
- width: 640px;
-}
-#media-actions {
- margin-top: 12px;
-}
-
-@keyframes spin {
- from {
- transform: rotateX(0deg);
- }
- to {
- transform: rotateX(180deg);
- }
-}
diff --git a/cast_receiver_app/app-desktop/html/index.html b/cast_receiver_app/app-desktop/html/index.html
deleted file mode 100644
index 19a118913b..0000000000
--- a/cast_receiver_app/app-desktop/html/index.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
- , number, string, !Callback): undefined}
- */
-const ActionHandler = undefined;
-
-/**
- * Dispatches messages of a cast message bus to registered action handlers.
- *
- * The dispatcher listens to events of a CastMessageBus for the namespace
- * passed to the constructor. The data
property of the event is
- * parsed as a json document and delegated to a handler registered for the given
- * method.
- */
-class MessageDispatcher {
- /**
- * @param {string} namespace The message namespace.
- * @param {!cast.framework.CastReceiverContext} castReceiverContext The cast
- * receiver manager.
- */
- constructor(namespace, castReceiverContext) {
- /** @private @const {string} */
- this.namespace_ = namespace;
- /** @private @const {!cast.framework.CastReceiverContext} */
- this.castReceiverContext_ = castReceiverContext;
- /** @private @const {!Array} */
- this.messageQueue_ = [];
- /** @private @const {!Object} */
- this.actions_ = {};
- /** @private @const {!Object} */
- this.senderSequences_ = {};
- /** @private @const {function(string, *)} */
- this.jsonStringifyReplacer_ = (key, value) => {
- if (value === Infinity || value === null) {
- return undefined;
- }
- return value;
- };
- this.castReceiverContext_.addCustomMessageListener(
- this.namespace_, this.onMessage.bind(this));
- }
-
- /**
- * Registers a handler of a given action.
- *
- * @param {string} method The method name for which to register the handler.
- * @param {!Array>} argDefs The name and type of each argument
- * or an empty array if the method has no arguments.
- * @param {!ActionHandler} handler A function to process the action.
- */
- registerActionHandler(method, argDefs, handler) {
- this.actions_[method] = {
- method,
- argDefs,
- handler,
- };
- }
-
- /**
- * Unregisters the handler of the given action.
- *
- * @param {string} action The action to unregister.
- */
- unregisterActionHandler(action) {
- delete this.actions_[action];
- }
-
- /**
- * Callback to receive messages sent by sender apps.
- *
- * @param {!cast.framework.system.Event} event The event received from the
- * sender app.
- */
- onMessage(event) {
- console.log('message arrived from sender', this.namespace_, event);
- const message = /** @type {!ExoCastMessage} */ (event.data);
- const action = this.actions_[message.method];
- if (action) {
- const args = message.args;
- for (let i = 0; i < action.argDefs.length; i++) {
- if (!validation.validateProperty(
- args, action.argDefs[i][0], action.argDefs[i][1])) {
- console.warn('invalid method call', message);
- return;
- }
- }
- this.messageQueue_.push({
- senderId: event.senderId,
- message: message,
- handler: action.handler
- });
- if (this.messageQueue_.length === 1) {
- this.executeNext();
- } else {
- // Do nothing. An action is executing asynchronously and will call
- // executeNext when finished.
- }
- } else {
- console.warn('handler of method not found', message);
- }
- }
-
- /**
- * Executes the next message in the queue.
- */
- executeNext() {
- if (this.messageQueue_.length === 0) {
- return;
- }
- const head = this.messageQueue_[0];
- const message = head.message;
- const senderSequence = message.sequenceNumber;
- this.senderSequences_[head.senderId] = senderSequence;
- try {
- head.handler(message.args, senderSequence, head.senderId, (response) => {
- if (response) {
- this.send(head.senderId, response);
- }
- this.shiftPendingMessage_(head);
- });
- } catch (e) {
- this.shiftPendingMessage_(head);
- console.error('error while executing method : ' + message.method, e);
- }
- }
-
- /**
- * Broadcasts the sender state to all sender apps registered for the
- * given message namespace.
- *
- * @param {!PlayerState} playerState The player state to be sent.
- */
- broadcast(playerState) {
- this.castReceiverContext_.getSenders().forEach((sender) => {
- this.send(sender.id, playerState);
- });
- delete playerState.sequenceNumber;
- }
-
- /**
- * Sends the PlayerState to the given sender.
- *
- * @param {string} senderId The id of the sender.
- * @param {!PlayerState} playerState The message to send.
- */
- send(senderId, playerState) {
- playerState.sequenceNumber = this.senderSequences_[senderId] || -1;
- this.castReceiverContext_.sendCustomMessage(
- this.namespace_, senderId,
- // TODO(bachinger) Find a better solution.
- JSON.parse(JSON.stringify(playerState, this.jsonStringifyReplacer_)));
- }
-
- /**
- * Notifies the message dispatcher that a given sender has disconnected from
- * the receiver.
- *
- * @param {string} senderId The id of the sender.
- */
- notifySenderDisconnected(senderId) {
- delete this.senderSequences_[senderId];
- }
-
- /**
- * Shifts the pending message and executes the next if any.
- *
- * @private
- * @param {!Message} pendingMessage The pending message.
- */
- shiftPendingMessage_(pendingMessage) {
- if (pendingMessage === this.messageQueue_[0]) {
- this.messageQueue_.shift();
- this.executeNext();
- }
- }
-}
-
-/**
- * An item in the message queue.
- *
- * @record
- */
-function Message() {}
-
-/**
- * The sender id.
- *
- * @type {string}
- */
-Message.prototype.senderId;
-
-/**
- * The ExoCastMessage sent by the sender app.
- *
- * @type {!ExoCastMessage}
- */
-Message.prototype.message;
-
-/**
- * The handler function handling the message.
- *
- * @type {!ActionHandler}
- */
-Message.prototype.handler;
-
-exports = MessageDispatcher;
diff --git a/cast_receiver_app/app/src/receiver.js b/cast_receiver_app/app/src/receiver.js
deleted file mode 100644
index 5e67219e75..0000000000
--- a/cast_receiver_app/app/src/receiver.js
+++ /dev/null
@@ -1,191 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- */
-
-goog.module('exoplayer.cast.Receiver');
-
-const MessageDispatcher = goog.require('exoplayer.cast.MessageDispatcher');
-const Player = goog.require('exoplayer.cast.Player');
-const validation = goog.require('exoplayer.cast.validation');
-
-/**
- * The Receiver receives messages from a message bus and delegates to
- * the player.
- *
- * @constructor
- * @param {!Player} player The player.
- * @param {!cast.framework.CastReceiverContext} context The cast receiver
- * context.
- * @param {!MessageDispatcher} messageDispatcher The message dispatcher to use.
- */
-const Receiver = function(player, context, messageDispatcher) {
- addPlayerActions(messageDispatcher, player);
- addQueueActions(messageDispatcher, player);
- player.addPlayerListener((playerState) => {
- messageDispatcher.broadcast(playerState);
- });
-
- context.addEventListener(
- cast.framework.system.EventType.SENDER_CONNECTED, (event) => {
- messageDispatcher.send(event.senderId, player.getPlayerState());
- });
-
- context.addEventListener(
- cast.framework.system.EventType.SENDER_DISCONNECTED, (event) => {
- messageDispatcher.notifySenderDisconnected(event.senderId);
- if (event.reason ===
- cast.framework.system.DisconnectReason.REQUESTED_BY_SENDER &&
- context.getSenders().length === 0) {
- window.close();
- }
- });
-
- // Start the cast receiver context.
- context.start();
-};
-
-/**
- * Registers action handlers for playback messages sent by the sender app.
- *
- * @param {!MessageDispatcher} messageDispatcher The dispatcher.
- * @param {!Player} player The player.
- */
-const addPlayerActions = function(messageDispatcher, player) {
- messageDispatcher.registerActionHandler(
- 'player.setPlayWhenReady', [['playWhenReady', 'boolean']],
- (args, senderSequence, senderId, callback) => {
- const playWhenReady = args['playWhenReady'];
- callback(
- !player.setPlayWhenReady(playWhenReady) ?
- player.getPlayerState() :
- null);
- });
- messageDispatcher.registerActionHandler(
- 'player.seekTo',
- [
- ['uuid', 'string'],
- ['positionMs', '?number'],
- ],
- (args, senderSequence, senderId, callback) => {
- callback(
- !player.seekToUuid(args['uuid'], args['positionMs']) ?
- player.getPlayerState() :
- null);
- });
- messageDispatcher.registerActionHandler(
- 'player.setRepeatMode', [['repeatMode', 'RepeatMode']],
- (args, senderSequence, senderId, callback) => {
- callback(
- !player.setRepeatMode(args['repeatMode']) ?
- player.getPlayerState() :
- null);
- });
- messageDispatcher.registerActionHandler(
- 'player.setShuffleModeEnabled', [['shuffleModeEnabled', 'boolean']],
- (args, senderSequence, senderId, callback) => {
- callback(
- !player.setShuffleModeEnabled(args['shuffleModeEnabled']) ?
- player.getPlayerState() :
- null);
- });
- messageDispatcher.registerActionHandler(
- 'player.onClientConnected', [],
- (args, senderSequence, senderId, callback) => {
- callback(player.getPlayerState());
- });
- messageDispatcher.registerActionHandler(
- 'player.stop', [['reset', 'boolean']],
- (args, senderSequence, senderId, callback) => {
- player.stop(args['reset']).then(() => {
- callback(null);
- });
- });
- messageDispatcher.registerActionHandler(
- 'player.prepare', [], (args, senderSequence, senderId, callback) => {
- player.prepare();
- callback(null);
- });
- messageDispatcher.registerActionHandler(
- 'player.setTrackSelectionParameters',
- [
- ['preferredAudioLanguage', 'string'],
- ['preferredTextLanguage', 'string'],
- ['disabledTextTrackSelectionFlags', 'Array'],
- ['selectUndeterminedTextLanguage', 'boolean'],
- ],
- (args, senderSequence, senderId, callback) => {
- const trackSelectionParameters =
- /** @type {!TrackSelectionParameters} */ ({
- preferredAudioLanguage: args['preferredAudioLanguage'],
- preferredTextLanguage: args['preferredTextLanguage'],
- disabledTextTrackSelectionFlags:
- args['disabledTextTrackSelectionFlags'],
- selectUndeterminedTextLanguage:
- args['selectUndeterminedTextLanguage'],
- });
- callback(
- !player.setTrackSelectionParameters(trackSelectionParameters) ?
- player.getPlayerState() :
- null);
- });
-};
-
-/**
- * Registers action handlers for queue management messages sent by the sender
- * app.
- *
- * @param {!MessageDispatcher} messageDispatcher The dispatcher.
- * @param {!Player} player The player.
- */
-const addQueueActions =
- function (messageDispatcher, player) {
- messageDispatcher.registerActionHandler(
- 'player.addItems',
- [
- ['index', '?number'],
- ['items', 'Array'],
- ['shuffleOrder', 'Array'],
- ],
- (args, senderSequence, senderId, callback) => {
- const mediaItems = args['items'];
- const index = args['index'] || player.getQueueSize();
- let addedItemCount;
- if (validation.validateMediaItems(mediaItems)) {
- addedItemCount =
- player.addQueueItems(index, mediaItems, args['shuffleOrder']);
- }
- callback(addedItemCount === 0 ? player.getPlayerState() : null);
- });
- messageDispatcher.registerActionHandler(
- 'player.removeItems', [['uuids', 'Array']],
- (args, senderSequence, senderId, callback) => {
- const removedItemsCount = player.removeQueueItems(args['uuids']);
- callback(removedItemsCount === 0 ? player.getPlayerState() : null);
- });
- messageDispatcher.registerActionHandler(
- 'player.moveItem',
- [
- ['uuid', 'string'],
- ['index', 'number'],
- ['shuffleOrder', 'Array'],
- ],
- (args, senderSequence, senderId, callback) => {
- const hasMoved = player.moveQueueItem(
- args['uuid'], args['index'], args['shuffleOrder']);
- callback(!hasMoved ? player.getPlayerState() : null);
- });
-};
-
-exports = Receiver;
diff --git a/cast_receiver_app/app/src/validation.js b/cast_receiver_app/app/src/validation.js
deleted file mode 100644
index 23e2708f8e..0000000000
--- a/cast_receiver_app/app/src/validation.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview A validator for messages received from sender apps.
- */
-
-goog.module('exoplayer.cast.validation');
-
-const {getPlaybackType, PlaybackType, RepeatMode} = goog.require('exoplayer.cast.constants');
-
-/**
- * Media item fields.
- *
- * @enum {string}
- */
-const MediaItemField = {
- UUID: 'uuid',
- MEDIA: 'media',
- MIME_TYPE: 'mimeType',
- DRM_SCHEMES: 'drmSchemes',
- TITLE: 'title',
- DESCRIPTION: 'description',
- START_POSITION_US: 'startPositionUs',
- END_POSITION_US: 'endPositionUs',
-};
-
-/**
- * DrmScheme fields.
- *
- * @enum {string}
- */
-const DrmSchemeField = {
- UUID: 'uuid',
- LICENSE_SERVER_URI: 'licenseServer',
-};
-
-/**
- * UriBundle fields.
- *
- * @enum {string}
- */
-const UriBundleField = {
- URI: 'uri',
- REQUEST_HEADERS: 'requestHeaders',
-};
-
-/**
- * Validates an array of media items.
- *
- * @param {!Array} mediaItems An array of media items.
- * @return {boolean} true if all media items are valid, otherwise false is
- * returned.
- */
-const validateMediaItems = function (mediaItems) {
- for (let i = 0; i < mediaItems.length; i++) {
- if (!validateMediaItem(mediaItems[i])) {
- return false;
- }
- }
- return true;
-};
-
-/**
- * Validates a queue item sent to the receiver by a sender app.
- *
- * @param {!MediaItem} mediaItem The media item.
- * @return {boolean} true if the media item is valid, false otherwise.
- */
-const validateMediaItem = function (mediaItem) {
- // validate minimal properties
- if (!validateProperty(mediaItem, MediaItemField.UUID, 'string')) {
- console.log('missing mandatory uuid', mediaItem.uuid);
- return false;
- }
- if (!validateProperty(mediaItem.media, UriBundleField.URI, 'string')) {
- console.log('missing mandatory', mediaItem.media ? 'uri' : 'media');
- return false;
- }
- const mimeType = mediaItem.mimeType;
- if (!mimeType || getPlaybackType(mimeType) === PlaybackType.UNKNOWN) {
- console.log('unsupported mime type:', mimeType);
- return false;
- }
- // validate optional properties
- if (goog.isArray(mediaItem.drmSchemes)) {
- for (let i = 0; i < mediaItem.drmSchemes.length; i++) {
- let drmScheme = mediaItem.drmSchemes[i];
- if (!validateProperty(drmScheme, DrmSchemeField.UUID, 'string') ||
- !validateProperty(
- drmScheme.licenseServer, UriBundleField.URI, 'string')) {
- console.log('invalid drm scheme', drmScheme);
- return false;
- }
- }
- }
- if (!validateProperty(mediaItem, MediaItemField.START_POSITION_US, '?number')
- || !validateProperty(mediaItem, MediaItemField.END_POSITION_US, '?number')
- || !validateProperty(mediaItem, MediaItemField.TITLE, '?string')
- || !validateProperty(mediaItem, MediaItemField.DESCRIPTION, '?string')) {
- console.log('invalid type of one of startPositionUs, endPositionUs, title'
- + ' or description', mediaItem);
- return false;
- }
- return true;
-};
-
-/**
- * Validates the existence and type of a property.
- *
- * Supported types: number, string, boolean, Array.
- *
Prefix the type with a ? to indicate that the property is optional.
- *
- * @param {?Object|?MediaItem|?UriBundle} obj The object to validate.
- * @param {string} propertyName The name of the property.
- * @param {string} type The type of the property.
- * @return {boolean} True if valid, false otherwise.
- */
-const validateProperty = function (obj, propertyName, type) {
- if (typeof obj === 'undefined' || obj === null) {
- return false;
- }
- const isOptional = type.startsWith('?');
- const value = obj[propertyName];
- if (isOptional && typeof value === 'undefined') {
- return true;
- }
- type = isOptional ? type.substring(1) : type;
- switch (type) {
- case 'string':
- return typeof value === 'string' || value instanceof String;
- case 'number':
- return typeof value === 'number' && isFinite(value);
- case 'Array':
- return typeof value !== 'undefined' && typeof value === 'object'
- && value.constructor === Array;
- case 'boolean':
- return typeof value === 'boolean';
- case 'RepeatMode':
- return value === RepeatMode.OFF || value === RepeatMode.ONE ||
- value === RepeatMode.ALL;
- default:
- console.warn('Unsupported type when validating an object property. ' +
- 'Supported types are string, number, boolean and Array.', type);
- return false;
- }
-};
-
-exports.validateMediaItem = validateMediaItem;
-exports.validateMediaItems = validateMediaItems;
-exports.validateProperty = validateProperty;
-
diff --git a/cast_receiver_app/assemble.bazel.sh b/cast_receiver_app/assemble.bazel.sh
deleted file mode 100755
index d2039a5152..0000000000
--- a/cast_receiver_app/assemble.bazel.sh
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2019 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.
-
-##
-# Assembles the html, css and javascript files which have been created by the
-# bazel build in a destination directory.
-
-HTML_DIR=app/html
-HTML_DEBUG_DIR=app-desktop/html
-BIN=bazel-bin
-
-function usage {
- echo "usage: `basename "$0"` -d=DESTINATION_DIR"
-}
-
-for i in "$@"
-do
-case $i in
- -d=*|--destination=*)
- DESTINATION="${i#*=}"
- shift # past argument=value
- ;;
- -h|--help)
- usage
- exit 0
- ;;
- *)
- # unknown option
- ;;
-esac
-done
-
-if [ ! -d "$DESTINATION" ]; then
- echo "destination directory '$DESTINATION' is not declared or is not a\
- directory"
- usage
- exit 1
-fi
-
-if [ ! -f "$BIN/app.js" ];then
- echo "file $BIN/app.js not found. Did you build already with bazel?"
- echo "-> # bazel build .. --incompatible_package_name_is_a_function=false"
- exit 1
-fi
-
-if [ ! -f "$BIN/app_desktop.js" ];then
- echo "file $BIN/app_desktop.js not found. Did you build already with bazel?"
- echo "-> # bazel build .. --incompatible_package_name_is_a_function=false"
- exit 1
-fi
-
-echo "assembling receiver and desktop app in $DESTINATION"
-echo "-------"
-
-# cleaning up asset files in destination directory
-FILES=(
- app.js
- app_desktop.js
- app_styles.css
- app_desktop_styles.css
- index.html
- player.html
-)
-for file in ${FILES[@]}; do
- if [ -f $DESTINATION/$file ]; then
- echo "deleting $file"
- rm -f $DESTINATION/$file
- fi
-done
-echo "-------"
-
-echo "copy html files to $DESTINATION"
-cp $HTML_DIR/index.html $DESTINATION
-cp $HTML_DEBUG_DIR/index.html $DESTINATION/player.html
-echo "copy javascript files to $DESTINATION"
-cp $BIN/app.js $BIN/app_desktop.js $DESTINATION
-echo "copy css style to $DESTINATION"
-cp $BIN/app_styles.css $BIN/app_desktop_styles.css $DESTINATION
-echo "-------"
-
-echo "done."
diff --git a/cast_receiver_app/externs/protocol.js b/cast_receiver_app/externs/protocol.js
deleted file mode 100644
index d6544a6f37..0000000000
--- a/cast_receiver_app/externs/protocol.js
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-/**
- * @fileoverview Externs for messages sent by a sender app in JSON format.
- *
- * Fields defined here are prevented from being renamed by the js compiler.
- *
- * @externs
- */
-
-/**
- * An uri bundle with an uri and request parameters.
- *
- * @record
- */
-class UriBundle {
- constructor() {
- /**
- * The URI.
- *
- * @type {string}
- */
- this.uri;
-
- /**
- * The request headers.
- *
- * @type {?Object}
- */
- this.requestHeaders;
- }
-}
-
-/**
- * @record
- */
-class DrmScheme {
- constructor() {
- /**
- * The DRM UUID.
- *
- * @type {string}
- */
- this.uuid;
-
- /**
- * The license URI.
- *
- * @type {?UriBundle}
- */
- this.licenseServer;
- }
-}
-
-/**
- * @record
- */
-class MediaItem {
- constructor() {
- /**
- * The uuid of the item.
- *
- * @type {string}
- */
- this.uuid;
-
- /**
- * The mime type.
- *
- * @type {string}
- */
- this.mimeType;
-
- /**
- * The media uri bundle.
- *
- * @type {!UriBundle}
- */
- this.media;
-
- /**
- * The DRM schemes.
- *
- * @type {!Array}
- */
- this.drmSchemes;
-
- /**
- * The position to start playback from.
- *
- * @type {number}
- */
- this.startPositionUs;
-
- /**
- * The position at which to end playback.
- *
- * @type {number}
- */
- this.endPositionUs;
-
- /**
- * The title of the media item.
- *
- * @type {string}
- */
- this.title;
-
- /**
- * The description of the media item.
- *
- * @type {string}
- */
- this.description;
- }
-}
-
-/**
- * Constraint parameters for track selection.
- *
- * @record
- */
-class TrackSelectionParameters {
- constructor() {
- /**
- * The preferred audio language.
- *
- * @type {string|undefined}
- */
- this.preferredAudioLanguage;
-
- /**
- * The preferred text language.
- *
- * @type {string|undefined}
- */
- this.preferredTextLanguage;
-
- /**
- * List of selection flags that are disabled for text track selections.
- *
- * @type {!Array}
- */
- this.disabledTextTrackSelectionFlags;
-
- /**
- * Whether a text track with undetermined language should be selected if no
- * track with `preferredTextLanguage` is available, or if
- * `preferredTextLanguage` is unset.
- *
- * @type {boolean}
- */
- this.selectUndeterminedTextLanguage;
- }
-}
-
-/**
- * The PlaybackPosition defined by the position, the uuid of the media item and
- * the period id.
- *
- * @record
- */
-class PlaybackPosition {
- constructor() {
- /**
- * The current playback position in milliseconds.
- *
- * @type {number}
- */
- this.positionMs;
-
- /**
- * The uuid of the media item.
- *
- * @type {string}
- */
- this.uuid;
-
- /**
- * The id of the currently playing period.
- *
- * @type {string}
- */
- this.periodId;
-
- /**
- * The reason of a position discontinuity if any.
- *
- * @type {?string}
- */
- this.discontinuityReason;
- }
-}
-
-/**
- * The playback parameters.
- *
- * @record
- */
-class PlaybackParameters {
- constructor() {
- /**
- * The playback speed.
- *
- * @type {number}
- */
- this.speed;
-
- /**
- * The playback pitch.
- *
- * @type {number}
- */
- this.pitch;
-
- /**
- * Whether silence is skipped.
- *
- * @type {boolean}
- */
- this.skipSilence;
- }
-}
-/**
- * The player state.
- *
- * @record
- */
-class PlayerState {
- constructor() {
- /**
- * The playback state.
- *
- * @type {string}
- */
- this.playbackState;
-
- /**
- * The playback parameters.
- *
- * @type {!PlaybackParameters}
- */
- this.playbackParameters;
-
- /**
- * Playback starts when ready if true.
- *
- * @type {boolean}
- */
- this.playWhenReady;
-
- /**
- * The current position within the media.
- *
- * @type {?PlaybackPosition}
- */
- this.playbackPosition;
-
- /**
- * The current window index.
- *
- * @type {number}
- */
- this.windowIndex;
-
- /**
- * The number of windows.
- *
- * @type {number}
- */
- this.windowCount;
-
- /**
- * The audio tracks.
- *
- * @type {!Array}
- */
- this.audioTracks;
-
- /**
- * The video tracks in case of adaptive media.
- *
- * @type {!Array>}
- */
- this.videoTracks;
-
- /**
- * The repeat mode.
- *
- * @type {string}
- */
- this.repeatMode;
-
- /**
- * Whether the shuffle mode is enabled.
- *
- * @type {boolean}
- */
- this.shuffleModeEnabled;
-
- /**
- * The playback order to use when shuffle mode is enabled.
- *
- * @type {!Array}
- */
- this.shuffleOrder;
-
- /**
- * The queue of media items.
- *
- * @type {!Array}
- */
- this.mediaQueue;
-
- /**
- * The media item info of the queue items if available.
- *
- * @type {!Object}
- */
- this.mediaItemsInfo;
-
- /**
- * The sequence number of the sender.
- *
- * @type {number}
- */
- this.sequenceNumber;
-
- /**
- * The player error.
- *
- * @type {?PlayerError}
- */
- this.error;
- }
-}
-
-/**
- * The error description.
- *
- * @record
- */
-class PlayerError {
- constructor() {
- /**
- * The error message.
- *
- * @type {string}
- */
- this.message;
-
- /**
- * The error code.
- *
- * @type {number}
- */
- this.code;
-
- /**
- * The error category.
- *
- * @type {number}
- */
- this.category;
- }
-}
-
-/**
- * A period.
- *
- * @record
- */
-class Period {
- constructor() {
- /**
- * The id of the period. Must be unique within a media item.
- *
- * @type {string}
- */
- this.id;
-
- /**
- * The duration of the period in microseconds.
- *
- * @type {number}
- */
- this.durationUs;
- }
-}
-/**
- * Holds dynamic information for a MediaItem.
- *
- * Holds information related to preparation for a specific {@link MediaItem}.
- * Unprepared items are associated with an {@link #EMPTY} info object until
- * prepared.
- *
- * @record
- */
-class MediaItemInfo {
- constructor() {
- /**
- * The duration of the window in microseconds.
- *
- * @type {number}
- */
- this.windowDurationUs;
-
- /**
- * The default start position relative to the start of the window in
- * microseconds.
- *
- * @type {number}
- */
- this.defaultStartPositionUs;
-
- /**
- * The periods conforming the media item.
- *
- * @type {!Array}
- */
- this.periods;
-
- /**
- * The position of the window in the first period in microseconds.
- *
- * @type {number}
- */
- this.positionInFirstPeriodUs;
-
- /**
- * Whether it is possible to seek within the window.
- *
- * @type {boolean}
- */
- this.isSeekable;
-
- /**
- * Whether the window may change when the timeline is updated.
- *
- * @type {boolean}
- */
- this.isDynamic;
- }
-}
-
-/**
- * The message envelope send by a sender app.
- *
- * @record
- */
-class ExoCastMessage {
- constructor() {
- /**
- * The clients message sequenec number.
- *
- * @type {number}
- */
- this.sequenceNumber;
-
- /**
- * The name of the method.
- *
- * @type {string}
- */
- this.method;
-
- /**
- * The arguments of the method.
- *
- * @type {!Object}
- */
- this.args;
- }
-};
-
diff --git a/cast_receiver_app/externs/shaka.js b/cast_receiver_app/externs/shaka.js
deleted file mode 100644
index 0af36d7b8c..0000000000
--- a/cast_receiver_app/externs/shaka.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-/**
- * @fileoverview Externs of the Shaka configuration.
- *
- * @externs
- */
-
-/**
- * The drm configuration for the Shaka player.
- *
- * @record
- */
-class DrmConfiguration {
- constructor() {
- /**
- * A map of license servers with the UUID of the drm system as the key and the
- * license uri as the value.
- *
- * @type {!Object}
- */
- this.servers;
- }
-}
-
-/**
- * The configuration of the Shaka player.
- *
- * @record
- */
-class PlayerConfiguration {
- constructor() {
- /**
- * The preferred audio language.
- *
- * @type {string}
- */
- this.preferredAudioLanguage;
-
- /**
- * The preferred text language.
- *
- * @type {string}
- */
- this.preferredTextLanguage;
-
- /**
- * The drm configuration.
- *
- * @type {?DrmConfiguration}
- */
- this.drm;
- }
-}
diff --git a/cast_receiver_app/src/configuration_factory.js b/cast_receiver_app/src/configuration_factory.js
deleted file mode 100644
index 819e52a755..0000000000
--- a/cast_receiver_app/src/configuration_factory.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-goog.module('exoplayer.cast.ConfigurationFactory');
-
-const {DRM_SYSTEMS} = goog.require('exoplayer.cast.constants');
-
-const EMPTY_DRM_CONFIGURATION =
- /** @type {!DrmConfiguration} */ (Object.freeze({
- servers: {},
- }));
-
-/**
- * Creates the configuration of the Shaka player.
- */
-class ConfigurationFactory {
- /**
- * Creates the Shaka player configuration.
- *
- * @param {!MediaItem} mediaItem The media item for which to create the
- * configuration.
- * @param {!TrackSelectionParameters} trackSelectionParameters The track
- * selection parameters.
- * @return {!PlayerConfiguration} The shaka player configuration.
- */
- createConfiguration(mediaItem, trackSelectionParameters) {
- const configuration = /** @type {!PlayerConfiguration} */ ({});
- this.mapLanguageConfiguration(trackSelectionParameters, configuration);
- this.mapDrmConfiguration_(mediaItem, configuration);
- return configuration;
- }
-
- /**
- * Maps the preferred audio and text language from the track selection
- * parameters to the configuration.
- *
- * @param {!TrackSelectionParameters} trackSelectionParameters The selection
- * parameters.
- * @param {!PlayerConfiguration} playerConfiguration The player configuration.
- */
- mapLanguageConfiguration(trackSelectionParameters, playerConfiguration) {
- playerConfiguration.preferredAudioLanguage =
- trackSelectionParameters.preferredAudioLanguage || '';
- playerConfiguration.preferredTextLanguage =
- trackSelectionParameters.preferredTextLanguage || '';
- }
-
- /**
- * Maps the drm configuration from the media item to the configuration. If no
- * drm is specified for the given media item, null is assigned.
- *
- * @private
- * @param {!MediaItem} mediaItem The media item.
- * @param {!PlayerConfiguration} playerConfiguration The player configuration.
- */
- mapDrmConfiguration_(mediaItem, playerConfiguration) {
- if (!mediaItem.drmSchemes) {
- playerConfiguration.drm = EMPTY_DRM_CONFIGURATION;
- return;
- }
- const drmConfiguration = /** @type {!DrmConfiguration} */({
- servers: {},
- });
- let hasDrmServer = false;
- mediaItem.drmSchemes.forEach((scheme) => {
- const drmSystem = DRM_SYSTEMS[scheme.uuid];
- if (drmSystem && scheme.licenseServer && scheme.licenseServer.uri) {
- hasDrmServer = true;
- drmConfiguration.servers[drmSystem] = scheme.licenseServer.uri;
- }
- });
- playerConfiguration.drm =
- hasDrmServer ? drmConfiguration : EMPTY_DRM_CONFIGURATION;
- }
-}
-
-exports = ConfigurationFactory;
diff --git a/cast_receiver_app/src/constants.js b/cast_receiver_app/src/constants.js
deleted file mode 100644
index e9600429f0..0000000000
--- a/cast_receiver_app/src/constants.js
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * Copyright (C) 2019 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.
- */
-
-goog.module('exoplayer.cast.constants');
-
-/**
- * The underyling player.
- *
- * @enum {number}
- */
-const PlaybackType = {
- VIDEO_ELEMENT: 1,
- SHAKA_PLAYER: 2,
- UNKNOWN: 999,
-};
-
-/**
- * Supported mime types and their playback mode.
- *
- * @type {!Object}
- */
-const SUPPORTED_MIME_TYPES = Object.freeze({
- 'application/dash+xml': PlaybackType.SHAKA_PLAYER,
- 'application/vnd.apple.mpegurl': PlaybackType.SHAKA_PLAYER,
- 'application/vnd.ms-sstr+xml': PlaybackType.SHAKA_PLAYER,
- 'application/x-mpegURL': PlaybackType.SHAKA_PLAYER,
-});
-
-/**
- * Returns the playback type required for a given mime type, or
- * PlaybackType.UNKNOWN if the mime type is not recognized.
- *
- * @param {string} mimeType The mime type.
- * @return {!PlaybackType} The required playback type, or PlaybackType.UNKNOWN
- * if the mime type is not recognized.
- */
-const getPlaybackType = function(mimeType) {
- if (mimeType.startsWith('video/') || mimeType.startsWith('audio/')) {
- return PlaybackType.VIDEO_ELEMENT;
- } else {
- return SUPPORTED_MIME_TYPES[mimeType] || PlaybackType.UNKNOWN;
- }
-};
-
-/**
- * Error messages.
- *
- * @enum {string}
- */
-const ErrorMessages = {
- SHAKA_LOAD_ERROR: 'Error while loading media with Shaka.',
- SHAKA_UNKNOWN_ERROR: 'Shaka error event captured.',
- MEDIA_ELEMENT_UNKNOWN_ERROR: 'Media element error event captured.',
- UNKNOWN_FATAL_ERROR: 'Fatal playback error. Shaka instance replaced.',
- UNKNOWN_ERROR: 'Unknown error',
-};
-
-/**
- * ExoPlayer's repeat modes.
- *
- * @enum {string}
- */
-const RepeatMode = {
- OFF: 'OFF',
- ONE: 'ONE',
- ALL: 'ALL',
-};
-
-/**
- * Error categories. Error categories coming from Shaka are defined in [Shaka
- * source
- * code](https://shaka-player-demo.appspot.com/docs/api/shaka.util.Error.html).
- *
- * @enum {number}
- */
-const ErrorCategory = {
- MEDIA_ELEMENT: 0,
- FATAL_SHAKA_ERROR: 1000,
-};
-
-/**
- * An error object to be used if no media error is assigned to the `error`
- * field of the media element when an error event is fired
- *
- * @type {!PlayerError}
- */
-const UNKNOWN_ERROR = /** @type {!PlayerError} */ (Object.freeze({
- message: ErrorMessages.UNKNOWN_ERROR,
- code: 0,
- category: 0,
-}));
-
-/**
- * UUID for the Widevine DRM scheme.
- *
- * @type {string}
- */
-const WIDEVINE_UUID = 'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed';
-
-/**
- * UUID for the PlayReady DRM scheme.
- *
- * @type {string}
- */
-const PLAYREADY_UUID = '9a04f079-9840-4286-ab92-e65be0885f95';
-
-/** @type {!Object} */
-const drmSystems = {};
-drmSystems[WIDEVINE_UUID] = 'com.widevine.alpha';
-drmSystems[PLAYREADY_UUID] = 'com.microsoft.playready';
-
-/**
- * The uuids of the supported DRM systems.
- *
- * @type {!Object}
- */
-const DRM_SYSTEMS = Object.freeze(drmSystems);
-
-exports.PlaybackType = PlaybackType;
-exports.ErrorMessages = ErrorMessages;
-exports.ErrorCategory = ErrorCategory;
-exports.RepeatMode = RepeatMode;
-exports.getPlaybackType = getPlaybackType;
-exports.WIDEVINE_UUID = WIDEVINE_UUID;
-exports.PLAYREADY_UUID = PLAYREADY_UUID;
-exports.DRM_SYSTEMS = DRM_SYSTEMS;
-exports.UNKNOWN_ERROR = UNKNOWN_ERROR;
diff --git a/cast_receiver_app/src/playback_info_view.js b/cast_receiver_app/src/playback_info_view.js
deleted file mode 100644
index 22e2b8ded5..0000000000
--- a/cast_receiver_app/src/playback_info_view.js
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-goog.module('exoplayer.cast.PlaybackInfoView');
-
-const Player = goog.require('exoplayer.cast.Player');
-const Timeout = goog.require('exoplayer.cast.Timeout');
-const dom = goog.require('goog.dom');
-
-/** The default timeout for hiding the UI in milliseconds. */
-const SHOW_TIMEOUT_MS = 5000;
-/** The timeout for hiding the UI in audio only mode in milliseconds. */
-const SHOW_TIMEOUT_MS_AUDIO = 0;
-/** The timeout for updating the UI while being displayed. */
-const UPDATE_TIMEOUT_MS = 1000;
-
-/**
- * Formats a duration in milliseconds to a string in hh:mm:ss format.
- *
- * @param {number} durationMs The duration in milliseconds.
- * @return {string} The duration formatted as hh:mm:ss.
- */
-const formatTimestampMsAsString = function (durationMs) {
- const hours = Math.floor(durationMs / 1000 / 60 / 60);
- const minutes = Math.floor((durationMs / 1000 / 60) % 60);
- const seconds = Math.floor((durationMs / 1000) % 60) % 60;
- let timeString = '';
- if (hours > 0) {
- timeString += hours + ':';
- }
- if (minutes < 10) {
- timeString += '0';
- }
- timeString += minutes + ":";
- if (seconds < 10) {
- timeString += '0';
- }
- timeString += seconds;
- return timeString;
-};
-
-/**
- * A view to display information about the current media item and playback
- * progress.
- *
- * @constructor
- * @param {!Player} player The player of which to display the
- * playback info.
- * @param {string} viewId The id of the playback info view.
- */
-const PlaybackInfoView = function (player, viewId) {
- /** @const @private {!Player} */
- this.player_ = player;
- /** @const @private {?Element} */
- this.container_ = document.getElementById(viewId);
- /** @const @private {?Element} */
- this.elapsedTimeBar_ = document.getElementById('exo_elapsed_time');
- /** @const @private {?Element} */
- this.elapsedTimeLabel_ = document.getElementById('exo_elapsed_time_label');
- /** @const @private {?Element} */
- this.durationLabel_ = document.getElementById('exo_duration_label');
- /** @const @private {!Timeout} */
- this.hideTimeout_ = new Timeout();
- /** @const @private {!Timeout} */
- this.updateTimeout_ = new Timeout();
- /** @private {boolean} */
- this.wasPlaying_ = player.getPlayWhenReady()
- && player.getPlaybackState() === Player.PlaybackState.READY;
- /** @private {number} */
- this.showTimeoutMs_ = SHOW_TIMEOUT_MS;
- /** @private {number} */
- this.showTimeoutMsVideo_ = this.showTimeoutMs_;
-
- if (this.wasPlaying_) {
- this.hideAfterTimeout();
- } else {
- this.show();
- }
-
- player.addPlayerListener((playerState) => {
- if (this.container_ === null) {
- return;
- }
- const playbackPosition = playerState.playbackPosition;
- const discontinuityReason =
- playbackPosition ? playbackPosition.discontinuityReason : null;
- if (discontinuityReason) {
- const currentMediaItem = player.getCurrentMediaItem();
- this.showTimeoutMs_ =
- currentMediaItem && currentMediaItem.mimeType === 'audio/*' ?
- SHOW_TIMEOUT_MS_AUDIO :
- this.showTimeoutMsVideo_;
- }
- const playWhenReady = playerState.playWhenReady;
- const state = playerState.playbackState;
- const isPlaying = playWhenReady && state === Player.PlaybackState.READY;
- const userSeekedInBufferedRange =
- discontinuityReason === Player.DiscontinuityReason.SEEK && isPlaying;
- if (!isPlaying) {
- this.show();
- } else if ((!this.wasPlaying_ && isPlaying) || userSeekedInBufferedRange) {
- this.hideAfterTimeout();
- }
- this.wasPlaying_ = isPlaying;
- });
-};
-
-/** Shows the player info view. */
-PlaybackInfoView.prototype.show = function () {
- if (this.container_ != null) {
- this.hideTimeout_.cancel();
- this.updateUi_();
- this.container_.style.display = 'block';
- this.startUpdateTimeout_();
- }
-};
-
-/** Hides the player info view. */
-PlaybackInfoView.prototype.hideAfterTimeout = function() {
- if (this.container_ === null) {
- return;
- }
- this.show();
- this.hideTimeout_.postDelayed(this.showTimeoutMs_).then(() => {
- this.container_.style.display = 'none';
- this.updateTimeout_.cancel();
- });
-};
-
-/**
- * Sets the playback info view timeout. The playback info view is automatically
- * hidden after this duration of time has elapsed without show() being called
- * again. When playing streams with content type 'audio/*' the view is always
- * displayed.
- *
- * @param {number} showTimeoutMs The duration in milliseconds. A non-positive
- * value will cause the view to remain visible indefinitely.
- */
-PlaybackInfoView.prototype.setShowTimeoutMs = function(showTimeoutMs) {
- this.showTimeoutMs_ = showTimeoutMs;
- this.showTimeoutMsVideo_ = showTimeoutMs;
-};
-
-/**
- * Updates all UI components.
- *
- * @private
- */
-PlaybackInfoView.prototype.updateUi_ = function () {
- const elapsedTimeMs = this.player_.getCurrentPositionMs();
- const durationMs = this.player_.getDurationMs();
- if (this.elapsedTimeLabel_ !== null) {
- this.updateDuration_(this.elapsedTimeLabel_, elapsedTimeMs, false);
- }
- if (this.durationLabel_ !== null) {
- this.updateDuration_(this.durationLabel_, durationMs, true);
- }
- if (this.elapsedTimeBar_ !== null) {
- this.updateProgressBar_(elapsedTimeMs, durationMs);
- }
-};
-
-/**
- * Adjust the progress bar indicating the elapsed time relative to the duration.
- *
- * @private
- * @param {number} elapsedTimeMs The elapsed time in milliseconds.
- * @param {number} durationMs The duration in milliseconds.
- */
-PlaybackInfoView.prototype.updateProgressBar_ =
- function(elapsedTimeMs, durationMs) {
- if (elapsedTimeMs <= 0 || durationMs <= 0) {
- this.elapsedTimeBar_.style.width = 0;
- } else {
- const widthPercentage = elapsedTimeMs / durationMs * 100;
- this.elapsedTimeBar_.style.width = Math.min(100, widthPercentage) + '%';
- }
-};
-
-/**
- * Updates the display value of the duration in the DOM formatted as hh:mm:ss.
- *
- * @private
- * @param {!Element} element The element to update.
- * @param {number} durationMs The duration in milliseconds.
- * @param {boolean} hideZero If true values of zero and below are not displayed.
- */
-PlaybackInfoView.prototype.updateDuration_ =
- function (element, durationMs, hideZero) {
- while (element.firstChild) {
- element.removeChild(element.firstChild);
- }
- if (durationMs <= 0 && !hideZero) {
- element.appendChild(dom.createDom(dom.TagName.SPAN, {},
- formatTimestampMsAsString(0)));
- } else if (durationMs > 0) {
- element.appendChild(dom.createDom(dom.TagName.SPAN, {},
- formatTimestampMsAsString(durationMs)));
- }
-};
-
-/**
- * Starts a repeating timeout that updates the UI every UPDATE_TIMEOUT_MS
- * milliseconds.
- *
- * @private
- */
-PlaybackInfoView.prototype.startUpdateTimeout_ = function() {
- this.updateTimeout_.cancel();
- if (!this.player_.getPlayWhenReady() ||
- this.player_.getPlaybackState() !== Player.PlaybackState.READY) {
- return;
- }
- this.updateTimeout_.postDelayed(UPDATE_TIMEOUT_MS).then(() => {
- this.updateUi_();
- this.startUpdateTimeout_();
- });
-};
-
-exports = PlaybackInfoView;
diff --git a/cast_receiver_app/src/player.js b/cast_receiver_app/src/player.js
deleted file mode 100644
index d7ffc58f4c..0000000000
--- a/cast_receiver_app/src/player.js
+++ /dev/null
@@ -1,1522 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-goog.module('exoplayer.cast.Player');
-
-const ConfigurationFactory = goog.require('exoplayer.cast.ConfigurationFactory');
-const NetworkingEngine = goog.require('shaka.net.NetworkingEngine');
-const ShakaError = goog.require('shaka.util.Error');
-const ShakaPlayer = goog.require('shaka.Player');
-const asserts = goog.require('goog.dom.asserts');
-const googArray = goog.require('goog.array');
-const safedom = goog.require('goog.dom.safe');
-const {ErrorMessages, ErrorCategory, PlaybackType, RepeatMode, getPlaybackType, UNKNOWN_ERROR} = goog.require('exoplayer.cast.constants');
-const {UuidComparator, createUuidComparator, log} = goog.require('exoplayer.cast.util');
-const {assert, fail} = goog.require('goog.asserts');
-const {clamp} = goog.require('goog.math');
-
-/**
- * Value indicating that no window index is currently set.
- */
-const INDEX_UNSET = -1;
-
-/**
- * Estimated time for processing the manifest after download in millisecconds.
- *
- * See: https://github.com/google/shaka-player/issues/1734
- */
-const MANIFEST_PROCESSING_ESTIMATE_MS = 350;
-
-/**
- * Media element events to listen to.
- *
- * @enum {string}
- */
-const MediaElementEvent = {
- ERROR: 'error',
- LOADED_DATA: 'loadeddata',
- PAUSE: 'pause',
- PLAYING: 'playing',
- SEEKED: 'seeked',
- SEEKING: 'seeking',
- WAITING: 'waiting',
-};
-
-/**
- * Shaka events to listen to.
- *
- * @enum {string}
- */
-const ShakaEvent = {
- ERROR: 'error',
- STREAMING: 'streaming',
- TRACKS_CHANGED: 'trackschanged',
-};
-
-/**
- * ExoPlayer's playback states.
- *
- * @enum {string}
- */
-const PlaybackState = {
- IDLE: 'IDLE',
- BUFFERING: 'BUFFERING',
- READY: 'READY',
- ENDED: 'ENDED',
-};
-
-/**
- * ExoPlayer's position discontinuity reasons.
- *
- * @enum {string}
- */
-const DiscontinuityReason = {
- PERIOD_TRANSITION: 'PERIOD_TRANSITION',
- SEEK: 'SEEK',
-};
-
-/**
- * A dummy `MediaIteminfo` to be used while the actual period is not
- * yet available.
- *
- * @const
- * @type {!MediaItemInfo}
- */
-const DUMMY_MEDIA_ITEM_INFO = Object.freeze({
- isSeekable: false,
- isDynamic: true,
- positionInFirstPeriodUs: 0,
- defaultStartPositionUs: 0,
- windowDurationUs: 0,
- periods: [{
- id: 1,
- durationUs: 0,
- }],
-});
-
-/**
- * The Player wraps a Shaka player and maintains a queue of media items.
- *
- * After construction the player is in `IDLE` state. Calling `#prepare` prepares
- * the player with the queue item at the given window index and position. The
- * state transitions to `BUFFERING`. When 'playWhenReady' is set to `true`
- * playback start when the player becomes 'READY'.
- *
- * When the player needs to rebuffer the state goes to 'BUFFERING' and becomes
- * 'READY' again when playback can be resumed.
- *
- * The state transitions to `ENDED` when playback reached the end of the last
- * item in the queue, when the last item has been removed from the queue if
- * `!IDLE`, or when `prepare` is called with an empty queue. Seeking makes the
- * player transition away from `ENDED` again.
- *
- * When `#stop` is called or when a fatal playback error occurs, the player
- * transition to `IDLE` state and needs to be prepared again to resume playback.
- *
- * `playWhenReady`, `repeatMode`, `shuffleModeEnabled` can be manipulated in any
- * state, just as media items can be added, moved and removed.
- *
- * @constructor
- * @param {!ShakaPlayer} shakaPlayer The shaka player to wrap.
- * @param {!ConfigurationFactory} configurationFactory A factory to create a
- * configuration for the Shaka player.
- */
-const Player = function(shakaPlayer, configurationFactory) {
- /** @private @const {?HTMLMediaElement} */
- this.videoElement_ = shakaPlayer.getMediaElement();
- /** @private @const {!ConfigurationFactory} */
- this.configurationFactory_ = configurationFactory;
- /** @private @const {!Array} */
- this.playerListeners_ = [];
- /**
- * @private
- * @const
- * {?function(NetworkingEngine.RequestType, (?|null))}
- */
- this.manifestResponseFilter_ = (type, response) => {
- if (type === NetworkingEngine.RequestType.MANIFEST) {
- setTimeout(() => {
- this.updateWindowMediaItemInfo_();
- this.invalidate();
- }, MANIFEST_PROCESSING_ESTIMATE_MS);
- }
- };
-
- /** @private {!ShakaPlayer} */
- this.shakaPlayer_ = shakaPlayer;
- /** @private {boolean} */
- this.playWhenReady_ = false;
- /** @private {boolean} */
- this.shuffleModeEnabled_ = false;
- /** @private {!RepeatMode} */
- this.repeatMode_ = RepeatMode.OFF;
- /** @private {!TrackSelectionParameters} */
- this.trackSelectionParameters_ = /** @type {!TrackSelectionParameters} */ ({
- preferredAudioLanguage: '',
- preferredTextLanguage: '',
- disabledTextTrackSelectionFlags: [],
- selectUndeterminedTextLanguage: false,
- });
- /** @private {number} */
- this.windowIndex_ = INDEX_UNSET;
- /** @private {!Array} */
- this.queue_ = [];
- /** @private {!Object} */
- this.queueUuidIndexMap_ = {};
- /** @private {!UuidComparator} */
- this.uuidComparator_ = createUuidComparator(this.queueUuidIndexMap_);
-
- /** @private {!PlaybackState} */
- this.playbackState_ = PlaybackState.IDLE;
- /** @private {!MediaItemInfo} */
- this.windowMediaItemInfo_ = DUMMY_MEDIA_ITEM_INFO;
- /** @private {number} */
- this.windowPeriodIndex_ = 0;
- /** @private {!Object} */
- this.mediaItemInfoMap_ = {};
- /** @private {?PlayerError} */
- this.playbackError_ = null;
- /** @private {?DiscontinuityReason} */
- this.discontinuityReason_ = null;
- /** @private {!Array} */
- this.shuffleOrder_ = [];
- /** @private {number} */
- this.shuffleIndex_ = 0;
- /** @private {!PlaybackType} */
- this.playbackType_ = PlaybackType.UNKNOWN;
- /** @private {boolean} */
- this.isManifestFilterRegistered_ = false;
- /** @private {?string} */
- this.uuidToPrepare_ = null;
-
- if (!this.shakaPlayer_ || !this.videoElement_) {
- throw new Error('an instance of Shaka player with a media element ' +
- 'attached to it needs to be passed to the constructor.');
- }
-
- /** @private @const {function(!Event)} */
- this.playbackStateListener_ = (ev) => {
- log(['handle event: ', ev.type]);
- let invalid = false;
- switch (ev.type) {
- case ShakaEvent.STREAMING: {
- // Arrives once after prepare when the manifest is available.
- const uuid = this.queue_[this.windowIndex_].uuid;
- const cachedMediaItemInfo = this.mediaItemInfoMap_[uuid];
- if (!cachedMediaItemInfo || cachedMediaItemInfo.isDynamic) {
- this.updateWindowMediaItemInfo_();
- if (this.windowMediaItemInfo_.isDynamic) {
- this.registerManifestResponseFilter_();
- }
- invalid = true;
- }
- break;
- }
- case ShakaEvent.TRACKS_CHANGED: {
- // Arrives when tracks have changed either initially or at a period
- // boundary.
- const periods = this.windowMediaItemInfo_.periods;
- const previousPeriodIndex = this.windowPeriodIndex_;
- this.evaluateAndSetCurrentPeriod_(periods);
- invalid = previousPeriodIndex !== this.windowPeriodIndex_;
- if (periods.length && this.windowPeriodIndex_ > 0) {
- // Player transitions to next period in multiperiod stream.
- this.discontinuityReason_ = this.discontinuityReason_ ||
- DiscontinuityReason.PERIOD_TRANSITION;
- invalid = true;
- }
- if (this.videoElement_.paused && this.playWhenReady_) {
- this.videoElement_.play();
- }
- break;
- }
- case MediaElementEvent.LOADED_DATA: {
- // Arrives once when the first frame has been rendered.
- if (this.playbackType_ === PlaybackType.VIDEO_ELEMENT) {
- const uuid = this.queue_[this.windowIndex_].uuid;
- let mediaItemInfo = this.mediaItemInfoMap_[uuid];
- if (!mediaItemInfo || mediaItemInfo.isDynamic) {
- mediaItemInfo = this.buildMediaItemInfoFromElement_();
- if (mediaItemInfo !== null) {
- this.mediaItemInfoMap_[uuid] = mediaItemInfo;
- this.windowMediaItemInfo_ = mediaItemInfo;
- }
- }
- this.evaluateAndSetCurrentPeriod_(mediaItemInfo.periods);
- invalid = true;
- }
- if (this.videoElement_.paused && this.playWhenReady_) {
- // Restart after automatic skip to next queue item.
- this.videoElement_.play();
- } else if (this.videoElement_.paused) {
- // If paused, the PLAYING event will not be fired, hence we transition
- // to state READY right here.
- this.playbackState_ = PlaybackState.READY;
- invalid = true;
- }
- break;
- }
- case MediaElementEvent.WAITING:
- case MediaElementEvent.SEEKING: {
- // Arrives at a user seek or when re-buffering starts.
- if (this.playbackState_ !== PlaybackState.BUFFERING) {
- this.playbackState_ = PlaybackState.BUFFERING;
- invalid = true;
- }
- break;
- }
- case MediaElementEvent.PLAYING:
- case MediaElementEvent.SEEKED: {
- // Arrives at the end of a user seek or after re-buffering.
- if (this.playbackState_ !== PlaybackState.READY) {
- this.playbackState_ = PlaybackState.READY;
- invalid = true;
- }
- break;
- }
- case MediaElementEvent.PAUSE: {
- // Detects end of media and either skips to next or transitions to ended
- // state.
- if (this.videoElement_.ended) {
- let nextWindowIndex = this.getNextWindowIndex();
- if (nextWindowIndex !== INDEX_UNSET) {
- this.seekToWindowInternal_(nextWindowIndex, undefined);
- } else {
- this.playbackState_ = PlaybackState.ENDED;
- invalid = true;
- }
- }
- break;
- }
- }
- if (invalid) {
- this.invalidate();
- }
- };
- /** @private @const {function(!Event)} */
- this.mediaElementErrorHandler_ = (ev) => {
- console.error('Media element error reported in handler');
- this.playbackError_ = !this.videoElement_.error ? UNKNOWN_ERROR : {
- message: this.videoElement_.error.message,
- code: this.videoElement_.error.code,
- category: ErrorCategory.MEDIA_ELEMENT,
- };
- this.playbackState_ = PlaybackState.IDLE;
- this.uuidToPrepare_ = this.queue_[this.windowIndex_] ?
- this.queue_[this.windowIndex_].uuid :
- null;
- this.invalidate();
- };
- /** @private @const {function(!Event)} */
- this.shakaErrorHandler_ = (ev) => {
- const shakaError = /** @type {!ShakaError} */ (ev['detail']);
- if (shakaError.severity !== ShakaError.Severity.RECOVERABLE) {
- this.fatalShakaError_(shakaError, 'Shaka error reported by error event');
- this.invalidate();
- } else {
- console.error('Recoverable Shaka error reported in handler');
- }
- };
-
- this.shakaPlayer_.addEventListener(
- ShakaEvent.STREAMING, this.playbackStateListener_);
- this.shakaPlayer_.addEventListener(
- ShakaEvent.TRACKS_CHANGED, this.playbackStateListener_);
-
- this.videoElement_.addEventListener(
- MediaElementEvent.LOADED_DATA, this.playbackStateListener_);
- this.videoElement_.addEventListener(
- MediaElementEvent.WAITING, this.playbackStateListener_);
- this.videoElement_.addEventListener(
- MediaElementEvent.PLAYING, this.playbackStateListener_);
- this.videoElement_.addEventListener(
- MediaElementEvent.PAUSE, this.playbackStateListener_);
- this.videoElement_.addEventListener(
- MediaElementEvent.SEEKING, this.playbackStateListener_);
- this.videoElement_.addEventListener(
- MediaElementEvent.SEEKED, this.playbackStateListener_);
-
- // Attach error handlers.
- this.shakaPlayer_.addEventListener(ShakaEvent.ERROR, this.shakaErrorHandler_);
- this.videoElement_.addEventListener(
- MediaElementEvent.ERROR, this.mediaElementErrorHandler_);
-};
-
-/**
- * Adds a listener to the player.
- *
- * @param {function(!PlayerState)} listener The player listener.
- */
-Player.prototype.addPlayerListener = function(listener) {
- this.playerListeners_.push(listener);
-};
-
-/**
- * Removes a listener.
- *
- * @param {function(!Object)} listener The player listener.
- */
-Player.prototype.removePlayerListener = function(listener) {
- for (let i = 0; i < this.playerListeners_.length; i++) {
- if (this.playerListeners_[i] === listener) {
- this.playerListeners_.splice(i, 1);
- break;
- }
- }
-};
-
-/**
- * Gets the current PlayerState.
- *
- * @return {!PlayerState}
- */
-Player.prototype.getPlayerState = function() {
- return this.buildPlayerState_();
-};
-
-/**
- * Sends the current playback state to clients.
- */
-Player.prototype.invalidate = function() {
- const playbackState = this.buildPlayerState_();
- for (let i = 0; i < this.playerListeners_.length; i++) {
- this.playerListeners_[i](playbackState);
- }
-};
-
-/**
- * Get the audio tracks.
- *
- * @return {!Array} An array with the track names}.
- */
-Player.prototype.getAudioTracks = function() {
- return this.windowMediaItemInfo_ !== DUMMY_MEDIA_ITEM_INFO ?
- this.shakaPlayer_.getAudioLanguages() :
- [];
-};
-
-/**
- * Gets the video tracks.
- *
- * @return {!Array} An array with the video tracks.
- */
-Player.prototype.getVideoTracks = function() {
- return this.windowMediaItemInfo_ !== DUMMY_MEDIA_ITEM_INFO ?
- this.shakaPlayer_.getVariantTracks() :
- [];
-};
-
-/**
- * Gets the playback state.
- *
- * @return {!PlaybackState} The playback state.
- */
-Player.prototype.getPlaybackState = function() {
- return this.playbackState_;
-};
-
-/**
- * Gets the playback error if any.
- *
- * @return {?Object} The playback error.
- */
-Player.prototype.getPlaybackError = function() {
- return this.playbackError_;
-};
-
-/**
- * Gets the duration in milliseconds or a negative value if unknown.
- *
- * @return {number} The duration in milliseconds.
- */
-Player.prototype.getDurationMs = function() {
- return this.windowMediaItemInfo_ ?
- this.windowMediaItemInfo_.windowDurationUs / 1000 : -1;
-};
-
-/**
- * Gets the current position in milliseconds or a negative value if not known.
- *
- * @return {number} The current position in milliseconds.
- */
-Player.prototype.getCurrentPositionMs = function() {
- if (!this.videoElement_.currentTime) {
- return 0;
- }
- return (this.videoElement_.currentTime * 1000) -
- (this.windowMediaItemInfo_.positionInFirstPeriodUs / 1000);
-};
-
-/**
- * Gets the current window index.
- *
- * @return {number} The current window index.
- */
-Player.prototype.getCurrentWindowIndex = function() {
- if (this.playbackState_ === PlaybackState.IDLE) {
- return this.queueUuidIndexMap_[this.uuidToPrepare_ || ''] || 0;
- }
- return Math.max(0, this.windowIndex_);
-};
-
-/**
- * Gets the media item of the current window or null if the queue is empty.
- *
- * @return {?MediaItem} The media item of the current window.
- */
-Player.prototype.getCurrentMediaItem = function() {
- return this.windowIndex_ >= 0 ? this.queue_[this.windowIndex_] : null;
-};
-
-/**
- * Gets the media item info of the current window index or null if not yet
- * available.
- *
- * @return {?MediaItemInfo} The current media item info or undefined.
- */
-Player.prototype.getCurrentMediaItemInfo = function () {
- return this.windowMediaItemInfo_;
-};
-
-/**
- * Gets the text tracks.
- *
- * @return {!TextTrackList} The text tracks.
- */
-Player.prototype.getTextTracks = function() {
- return this.videoElement_.textTracks;
-};
-
-/**
- * Gets whether the player should play when ready.
- *
- * @return {boolean} True when it plays when ready.
- */
-Player.prototype.getPlayWhenReady = function() {
- return this.playWhenReady_;
-};
-
-/**
- * Sets whether to play when ready.
- *
- * @param {boolean} playWhenReady Whether to play when ready.
- * @return {boolean} Whether calling this method causes a change of the player
- * state.
- */
-Player.prototype.setPlayWhenReady = function(playWhenReady) {
- if (this.playWhenReady_ === playWhenReady) {
- return false;
- }
- this.playWhenReady_ = playWhenReady;
- this.invalidate();
- if (this.playbackState_ === PlaybackState.IDLE ||
- this.playbackState_ === PlaybackState.ENDED) {
- return true;
- }
- if (this.playWhenReady_) {
- this.videoElement_.play();
- } else {
- this.videoElement_.pause();
- }
- return true;
-};
-
-/**
- * Gets the repeat mode.
- *
- * @return {!RepeatMode} The repeat mode.
- */
-Player.prototype.getRepeatMode = function() {
- return this.repeatMode_;
-};
-
-/**
- * Sets the repeat mode. Must be a value of the enum Player.RepeatMode.
- *
- * @param {!RepeatMode} mode The repeat mode.
- * @return {boolean} Whether calling this method causes a change of the player
- * state.
- */
-Player.prototype.setRepeatMode = function(mode) {
- if (this.repeatMode_ === mode) {
- return false;
- }
- if (mode === Player.RepeatMode.OFF ||
- mode === Player.RepeatMode.ONE ||
- mode === Player.RepeatMode.ALL) {
- this.repeatMode_ = mode;
- } else {
- throw new Error('illegal repeat mode: ' + mode);
- }
- this.invalidate();
- return true;
-};
-
-/**
- * Enables or disables the shuffle mode.
- *
- * @param {boolean} enabled Whether the shuffle mode is enabled or not.
- * @return {boolean} Whether calling this method causes a change of the player
- * state.
- */
-Player.prototype.setShuffleModeEnabled = function(enabled) {
- if (this.shuffleModeEnabled_ === enabled) {
- return false;
- }
- this.shuffleModeEnabled_ = enabled;
- this.invalidate();
- return true;
-};
-
-/**
- * Sets the track selection parameters.
- *
- * @param {!TrackSelectionParameters} trackSelectionParameters The parameters.
- * @return {boolean} Whether calling this method causes a change of the player
- * state.
- */
-Player.prototype.setTrackSelectionParameters = function(
- trackSelectionParameters) {
- this.trackSelectionParameters_ = trackSelectionParameters;
- /** @type {!PlayerConfiguration} */
- const configuration = /** @type {!PlayerConfiguration} */ ({});
- this.configurationFactory_.mapLanguageConfiguration(
- trackSelectionParameters, configuration);
- /** @type {!PlayerConfiguration} */
- const currentConfiguration = this.shakaPlayer_.getConfiguration();
- /** @type {boolean} */
- let isStateChange = false;
- if (currentConfiguration.preferredAudioLanguage !==
- configuration.preferredAudioLanguage) {
- this.shakaPlayer_.selectAudioLanguage(configuration.preferredAudioLanguage);
- isStateChange = true;
- }
- if (currentConfiguration.preferredTextLanguage !==
- configuration.preferredTextLanguage) {
- this.shakaPlayer_.selectTextLanguage(configuration.preferredTextLanguage);
- isStateChange = true;
- }
- return isStateChange;
-};
-
-/**
- * Gets the previous window index or a negative number if no item previous to
- * the current item is available.
- *
- * @return {number} The previous window index or a negative number if the
- * current item is the first item.
- */
-Player.prototype.getPreviousWindowIndex = function() {
- if (this.playbackType_ === PlaybackType.UNKNOWN) {
- return INDEX_UNSET;
- }
- switch (this.repeatMode_) {
- case RepeatMode.ONE:
- return this.windowIndex_;
- case RepeatMode.ALL:
- if (this.shuffleModeEnabled_) {
- const previousIndex = this.shuffleIndex_ > 0 ?
- this.shuffleIndex_ - 1 : this.queue_.length - 1;
- return this.shuffleOrder_[previousIndex];
- } else {
- const previousIndex = this.windowIndex_ > 0 ?
- this.windowIndex_ - 1 : this.queue_.length - 1;
- return previousIndex;
- }
- break;
- case RepeatMode.OFF:
- if (this.shuffleModeEnabled_) {
- const previousIndex = this.shuffleIndex_ - 1;
- return previousIndex < 0 ? -1 : this.shuffleOrder_[previousIndex];
- } else {
- const previousIndex = this.windowIndex_ - 1;
- return previousIndex < 0 ? -1 : previousIndex;
- }
- break;
- default:
- throw new Error('illegal state of repeat mode: ' + this.repeatMode_);
- }
-};
-
-/**
- * Gets the next window index or a negative number if the current item is the
- * last item.
- *
- * @return {number} The next window index or a negative number if the current
- * item is the last item.
- */
-Player.prototype.getNextWindowIndex = function() {
- if (this.playbackType_ === PlaybackType.UNKNOWN) {
- return INDEX_UNSET;
- }
- switch (this.repeatMode_) {
- case RepeatMode.ONE:
- return this.windowIndex_;
- case RepeatMode.ALL:
- if (this.shuffleModeEnabled_) {
- const nextIndex = (this.shuffleIndex_ + 1) % this.queue_.length;
- return this.shuffleOrder_[nextIndex];
- } else {
- return (this.windowIndex_ + 1) % this.queue_.length;
- }
- break;
- case RepeatMode.OFF:
- if (this.shuffleModeEnabled_) {
- const nextIndex = this.shuffleIndex_ + 1;
- return nextIndex < this.shuffleOrder_.length ?
- this.shuffleOrder_[nextIndex] : -1;
- } else {
- const nextIndex = this.windowIndex_ + 1;
- return nextIndex < this.queue_.length ? nextIndex : -1;
- }
- break;
- default:
- throw new Error('illegal state of repeat mode: ' + this.repeatMode_);
- }
-};
-
-/**
- * Gets whether the current window is seekable.
- *
- * @return {boolean} True if seekable.
- */
-Player.prototype.isCurrentWindowSeekable = function() {
- return !!this.videoElement_.seekable;
-};
-
-/**
- * Seeks to the positionMs of the media item with the given uuid.
- *
- * @param {string} uuid The uuid of the media item to seek to.
- * @param {number|undefined} positionMs The position in milliseconds to seek to.
- * @return {boolean} True if a seek operation has been processed, false
- * otherwise.
- */
-Player.prototype.seekToUuid = function(uuid, positionMs) {
- if (this.playbackState_ === PlaybackState.IDLE) {
- this.uuidToPrepare_ = uuid;
- this.videoElement_.currentTime =
- this.getPosition_(positionMs, INDEX_UNSET) / 1000;
- this.invalidate();
- return true;
- }
- const windowIndex = this.queueUuidIndexMap_[uuid];
- if (windowIndex !== undefined) {
- positionMs = this.getPosition_(positionMs, windowIndex);
- this.discontinuityReason_ = DiscontinuityReason.SEEK;
- this.seekToWindowInternal_(windowIndex, positionMs);
- return true;
- }
- return false;
-};
-
-/**
- * Seeks to the positionMs of the given window.
- *
- * The index must be a valid index of the current queue, else this method does
- * nothing.
- *
- * @param {number} windowIndex The index of the window to seek to.
- * @param {number|undefined} positionMs The position to seek to within the
- * window.
- */
-Player.prototype.seekToWindow = function(windowIndex, positionMs) {
- if (windowIndex < 0 || windowIndex >= this.queue_.length) {
- return;
- }
- this.seekToUuid(this.queue_[windowIndex].uuid, positionMs);
-};
-
-/**
- * Gets the number of media items in the queue.
- *
- * @return {number} The size of the queue.
- */
-Player.prototype.getQueueSize = function() {
- return this.queue_.length;
-};
-
-/**
- * Adds an array of items at the given index of the queue.
- *
- * Items are expected to have been validated with `validation#validateMediaItem`
- * or `validation#validateMediaItems` before being passed to this method.
- *
- * @param {number} index The index where to insert the media item.
- * @param {!Array} mediaItems The media items.
- * @param {!Array|undefined} shuffleOrder The new shuffle order.
- * @return {number} The number of added items.
- */
-Player.prototype.addQueueItems = function(index, mediaItems, shuffleOrder) {
- if (index < 0 || mediaItems.length === 0) {
- return 0;
- }
- let addedItemCount = 0;
- index = Math.min(this.queue_.length, index);
- mediaItems.forEach((itemToAdd) => {
- if (this.queueUuidIndexMap_[itemToAdd.uuid] === undefined) {
- this.queue_.splice(index + addedItemCount, 0, itemToAdd);
- this.queueUuidIndexMap_[itemToAdd.uuid] = index + addedItemCount;
- addedItemCount++;
- }
- });
- if (addedItemCount === 0) {
- return 0;
- }
- this.buildUuidIndexMap_(index + addedItemCount);
- this.setShuffleOrder_(shuffleOrder);
- if (this.queue_.length === addedItemCount) {
- this.windowIndex_ = 0;
- this.updateShuffleIndex_();
- } else if (
- index <= this.windowIndex_ &&
- this.playbackType_ !== PlaybackType.UNKNOWN) {
- this.windowIndex_ += mediaItems.length;
- this.updateShuffleIndex_();
- }
- this.invalidate();
- return addedItemCount;
-};
-
-/**
- * Removes the queue items with the given uuids.
- *
- * @param {!Array} uuids The uuids of the queue items to remove.
- * @return {number} The number of items removed from the queue.
- */
-Player.prototype.removeQueueItems = function(uuids) {
- let currentWindowRemoved = false;
- let lowestIndexRemoved = this.queue_.length - 1;
- const initialQueueSize = this.queue_.length;
- // Sort in descending order to start removing from the end.
- uuids = uuids.sort(this.uuidComparator_);
- uuids.forEach((uuid) => {
- const indexToRemove = this.queueUuidIndexMap_[uuid];
- if (indexToRemove === undefined) {
- return;
- }
- // Remove the item from the queue.
- this.queue_.splice(indexToRemove, 1);
- // Remove the corresponding media item info.
- delete this.mediaItemInfoMap_[uuid];
- // Remove the mapping to the window index.
- delete this.queueUuidIndexMap_[uuid];
- lowestIndexRemoved = Math.min(lowestIndexRemoved, indexToRemove);
- currentWindowRemoved =
- currentWindowRemoved || indexToRemove === this.windowIndex_;
- // The window index needs to be decreased when the item which has been
- // removed was before the current item, when the current item at the last
- // position has been removed, or when the queue has been emptied.
- if (indexToRemove < this.windowIndex_ ||
- (indexToRemove === this.windowIndex_ &&
- indexToRemove === this.queue_.length) ||
- this.queue_.length === 0) {
- this.windowIndex_--;
- }
- // Adjust the shuffle order.
- let shuffleIndexToRemove;
- this.shuffleOrder_.forEach((windowIndex, index) => {
- if (windowIndex > indexToRemove) {
- // Decrease the index in the shuffle order.
- this.shuffleOrder_[index]--;
- } else if (windowIndex === indexToRemove) {
- // Recall index for removal after traversing.
- shuffleIndexToRemove = index;
- }
- });
- // Remove the shuffle order entry of the removed item.
- this.shuffleOrder_.splice(shuffleIndexToRemove, 1);
- });
- const removedItemsCount = initialQueueSize - this.queue_.length;
- if (removedItemsCount === 0) {
- return 0;
- }
- this.updateShuffleIndex_();
- this.buildUuidIndexMap_(lowestIndexRemoved);
- if (currentWindowRemoved) {
- if (this.queue_.length === 0) {
- this.playbackState_ = this.playbackState_ === PlaybackState.IDLE ?
- PlaybackState.IDLE :
- PlaybackState.ENDED;
- this.windowMediaItemInfo_ = DUMMY_MEDIA_ITEM_INFO;
- this.windowPeriodIndex_ = 0;
- this.videoElement_.currentTime = 0;
- this.uuidToPrepare_ = null;
- this.unregisterManifestResponseFilter_();
- this.unload_(/** reinitialiseMediaSource= */ true);
- } else if (this.windowIndex_ >= 0) {
- const windowIndexToPrepare = this.windowIndex_;
- this.windowIndex_ = INDEX_UNSET;
- this.seekToWindowInternal_(windowIndexToPrepare, undefined);
- return removedItemsCount;
- }
- }
- this.invalidate();
- return removedItemsCount;
-};
-
-/**
- * Move the queue item with the given id to the given position.
- *
- * @param {string} uuid The uuid of the queue item to move.
- * @param {number} to The position to move the item to.
- * @param {!Array|undefined} shuffleOrder The new shuffle order.
- * @return {boolean} Whether the item has been moved.
- */
-Player.prototype.moveQueueItem = function(uuid, to, shuffleOrder) {
- if (to < 0 || to >= this.queue_.length) {
- return false;
- }
- const windowIndex = this.queueUuidIndexMap_[uuid];
- if (windowIndex === undefined) {
- return false;
- }
- const itemMoved = this.moveInQueue_(windowIndex, to);
- if (itemMoved) {
- this.setShuffleOrder_(shuffleOrder);
- this.invalidate();
- }
- return itemMoved;
-};
-
-/**
- * Prepares the player at the current window index and position.
- *
- * The playback state immediately transitions to `BUFFERING`. If the queue
- * is empty the player transitions to `ENDED`.
- */
-Player.prototype.prepare = function() {
- if (this.queue_.length === 0) {
- this.uuidToPrepare_ = null;
- this.playbackState_ = PlaybackState.ENDED;
- this.invalidate();
- return;
- }
- if (this.uuidToPrepare_) {
- this.windowIndex_ =
- this.queueUuidIndexMap_[this.uuidToPrepare_] || INDEX_UNSET;
- this.uuidToPrepare_ = null;
- }
- this.windowIndex_ = clamp(this.windowIndex_, 0, this.queue_.length - 1);
- this.prepare_(this.getCurrentPositionMs());
- this.invalidate();
-};
-
-/**
- * Stops the player.
- *
- * Calling this method causes the player to transition into `IDLE` state.
- * If `reset` is `true` the player is reset to the initial state of right
- * after construction. If `reset` is `false`, the media queue is preserved
- * and calling `prepare()` results in resuming the player state to what it
- * was before calling `#stop(false)`.
- *
- * @param {boolean} reset Whether the state should be reset.
- * @return {!Promise} A promise which resolves after async unload
- * tasks have finished.
- */
-Player.prototype.stop = function(reset) {
- this.playbackState_ = PlaybackState.IDLE;
- this.playbackError_ = null;
- this.discontinuityReason_ = null;
- this.unregisterManifestResponseFilter_();
- this.uuidToPrepare_ = this.uuidToPrepare_ || (this.queue_[this.windowIndex_] ?
- this.queue_[this.windowIndex_].uuid :
- null);
- if (reset) {
- this.uuidToPrepare_ = null;
- this.queue_ = [];
- this.queueUuidIndexMap_ = {};
- this.uuidComparator_ = createUuidComparator(this.queueUuidIndexMap_);
- this.windowIndex_ = INDEX_UNSET;
- this.mediaItemInfoMap_ = {};
- this.windowMediaItemInfo_ = DUMMY_MEDIA_ITEM_INFO;
- this.windowPeriodIndex_ = 0;
- this.videoElement_.currentTime = 0;
- this.shuffleOrder_ = [];
- this.shuffleIndex_ = 0;
- }
- this.invalidate();
- return this.unload_(/** reinitialiseMediaSource= */ !reset);
-};
-
-/**
- * Resets player and media element.
- *
- * @private
- * @param {boolean} reinitialiseMediaSource Whether the media source should be
- * reinitialized.
- * @return {!Promise} A promise which resolves after async unload
- * tasks have finished.
- */
-Player.prototype.unload_ = function(reinitialiseMediaSource) {
- const playbackTypeToUnload = this.playbackType_;
- this.playbackType_ = PlaybackType.UNKNOWN;
- switch (playbackTypeToUnload) {
- case PlaybackType.VIDEO_ELEMENT:
- this.videoElement_.removeAttribute('src');
- this.videoElement_.load();
- return Promise.resolve();
- case PlaybackType.SHAKA_PLAYER:
- return new Promise((resolve, reject) => {
- this.shakaPlayer_.unload(reinitialiseMediaSource)
- .then(resolve)
- .catch(resolve);
- });
- default:
- return Promise.resolve();
- }
-};
-
-/**
- * Releases the current Shaka instance and create a new one.
- *
- * This function should only be called if the Shaka instance is out of order due
- * to https://github.com/google/shaka-player/issues/1785. It assumes the current
- * Shaka instance has fallen into a state in which promises returned by
- * `shakaPlayer.load` and `shakaPlayer.unload` do not resolve nor are they
- * rejected anymore.
- *
- * @private
- */
-Player.prototype.replaceShaka_ = function() {
- // Remove all listeners.
- this.shakaPlayer_.removeEventListener(
- ShakaEvent.STREAMING, this.playbackStateListener_);
- this.shakaPlayer_.removeEventListener(
- ShakaEvent.TRACKS_CHANGED, this.playbackStateListener_);
- this.shakaPlayer_.removeEventListener(
- ShakaEvent.ERROR, this.shakaErrorHandler_);
- // Unregister response filter if any.
- this.unregisterManifestResponseFilter_();
- // Unload the old instance.
- this.shakaPlayer_.unload(false);
- // Reset video element.
- this.videoElement_.removeAttribute('src');
- this.videoElement_.load();
- // Create a new instance and add listeners.
- this.shakaPlayer_ = new ShakaPlayer(this.videoElement_);
- this.shakaPlayer_.addEventListener(
- ShakaEvent.STREAMING, this.playbackStateListener_);
- this.shakaPlayer_.addEventListener(
- ShakaEvent.TRACKS_CHANGED, this.playbackStateListener_);
- this.shakaPlayer_.addEventListener(ShakaEvent.ERROR, this.shakaErrorHandler_);
-};
-
-/**
- * Moves a queue item within the queue.
- *
- * @private
- * @param {number} from The initial position.
- * @param {number} to The position to move the item to.
- * @return {boolean} Whether the item has been moved.
- */
-Player.prototype.moveInQueue_ = function(from, to) {
- if (from < 0 || to < 0
- || from >= this.queue_.length || to >= this.queue_.length
- || from === to) {
- return false;
- }
- this.queue_.splice(to, 0, this.queue_.splice(from, 1)[0]);
- this.buildUuidIndexMap_(Math.min(from, to));
- if (from === this.windowIndex_) {
- this.windowIndex_ = to;
- } else if (from > this.windowIndex_ && to <= this.windowIndex_) {
- this.windowIndex_++;
- } else if (from < this.windowIndex_ && to >= this.windowIndex_) {
- this.windowIndex_--;
- }
- return true;
-};
-
-/**
- * Shuffles the queue.
- *
- * @private
- */
-Player.prototype.shuffle_ = function() {
- this.shuffleOrder_ = this.queue_.map((item, index) => index);
- googArray.shuffle(this.shuffleOrder_);
- this.updateShuffleIndex_();
-};
-
-/**
- * Sets the new shuffle order.
- *
- * @private
- * @param {!Array|undefined} shuffleOrder The new shuffle order.
- */
-Player.prototype.setShuffleOrder_ = function(shuffleOrder) {
- if (shuffleOrder && this.queue_.length === shuffleOrder.length) {
- this.shuffleOrder_ = shuffleOrder;
- this.updateShuffleIndex_();
- } else if (this.shuffleOrder_.length !== this.queue_.length) {
- this.shuffle_();
- }
-};
-
-/**
- * Updates the shuffle order to point to the current window index.
- *
- * @private
- */
-Player.prototype.updateShuffleIndex_ = function() {
- this.shuffleIndex_ =
- this.shuffleOrder_.findIndex((idx) => idx === this.windowIndex_);
-};
-
-/**
- * Builds the `queueUuidIndexMap` using the uuid of a media item as the key and
- * the window index as the value of an entry.
- *
- * @private
- * @param {number} startPosition The window index to start updating at.
- */
-Player.prototype.buildUuidIndexMap_ = function(startPosition) {
- for (let i = startPosition; i < this.queue_.length; i++) {
- this.queueUuidIndexMap_[this.queue_[i].uuid] = i;
- }
-};
-
-/**
- * Gets the default position of the current window.
- *
- * @private
- * @return {number} The default position of the current window.
- */
-Player.prototype.getDefaultPosition_ = function() {
- return this.windowMediaItemInfo_.defaultStartPositionUs;
-};
-
-/**
- * Checks whether the given position is buffered.
- *
- * @private
- * @param {number} positionMs The position to check.
- * @return {boolean} true if the media data of the current position is buffered.
- */
-Player.prototype.isBuffered_ = function(positionMs) {
- const ranges = this.videoElement_.buffered;
- for (let i = 0; i < ranges.length; i++) {
- const start = ranges.start(i) * 1000;
- const end = ranges.end(i) * 1000;
- if (start <= positionMs && positionMs <= end) {
- return true;
- }
- }
- return false;
-};
-
-/**
- * Seeks to the positionMs of the given window.
- *
- * To signal a user seek, callers are expected to set the discontinuity reason
- * to `DiscontinuityReason.SEEK` before calling this method. If not set this
- * method may set the `DiscontinuityReason.PERIOD_TRANSITION` in case the
- * `windowIndex` changes.
- *
- * @private
- * @param {number} windowIndex The non-negative index of the window to seek to.
- * @param {number|undefined} positionMs The position to seek to within the
- * window. If undefined it seeks to the default position of the window.
- */
-Player.prototype.seekToWindowInternal_ = function(windowIndex, positionMs) {
- const windowChanges = this.windowIndex_ !== windowIndex;
- // Update window index and position in any case.
- this.windowIndex_ = Math.max(0, windowIndex);
- this.updateShuffleIndex_();
- const seekPositionMs = this.getPosition_(positionMs, windowIndex);
- this.videoElement_.currentTime = seekPositionMs / 1000;
-
- // IDLE or ENDED with empty queue.
- if (this.playbackState_ === PlaybackState.IDLE || this.queue_.length === 0) {
- // Do nothing but report the change in window index and position.
- this.invalidate();
- return;
- }
-
- // Prepare for a seek to another window or when in ENDED state whilst the
- // queue is not empty but prepare has not been called yet.
- if (windowChanges || this.playbackType_ === PlaybackType.UNKNOWN) {
- // Reset and prepare.
- this.unregisterManifestResponseFilter_();
- this.discontinuityReason_ =
- this.discontinuityReason_ || DiscontinuityReason.PERIOD_TRANSITION;
- this.prepare_(seekPositionMs);
- this.invalidate();
- return;
- }
-
- // Sync playWhenReady with video element after ENDED state.
- if (this.playbackState_ === PlaybackState.ENDED && this.playWhenReady_) {
- this.videoElement_.play();
- return;
- }
-
- // A seek within the current window when READY or BUFFERING.
- this.playbackState_ = this.isBuffered_(seekPositionMs) ?
- PlaybackState.READY :
- PlaybackState.BUFFERING;
- this.invalidate();
-};
-
-/**
- * Prepares the player at the current window index and the given
- * `startPositionMs`.
- *
- * Calling this method resets the media item information, transitions to
- * 'BUFFERING', prepares either the plain video element for progressive
- * media, or the Shaka player for adaptive media.
- *
- * Media items are mapped by media type to a `PlaybackType`s in
- * `exoplayer.cast.constants.SupportedMediaTypes`. Unsupported mime types will
- * cause the player to transition to the `IDLE` state.
- *
- * Items in the queue are expected to have been validated with
- * `validation#validateMediaItem` or `validation#validateMediaItems`. If this is
- * not the case this method might throw an Assertion exception.
- *
- * @private
- * @param {number} startPositionMs The position at which to start playback.
- * @throws {!AssertionException} In case an unvalidated item can't be mapped to
- * a supported playback type.
- */
-Player.prototype.prepare_ = function(startPositionMs) {
- const mediaItem = this.queue_[this.windowIndex_];
- const windowUuid = this.queue_[this.windowIndex_].uuid;
- const mediaItemInfo = this.mediaItemInfoMap_[windowUuid];
- if (mediaItemInfo && !mediaItemInfo.isDynamic) {
- // Do reuse if not dynamic.
- this.windowMediaItemInfo_ = mediaItemInfo;
- } else {
- // Use the dummy info until manifest/data available.
- this.windowMediaItemInfo_ = DUMMY_MEDIA_ITEM_INFO;
- this.mediaItemInfoMap_[windowUuid] = DUMMY_MEDIA_ITEM_INFO;
- }
- this.windowPeriodIndex_ = 0;
- this.playbackType_ = getPlaybackType(mediaItem.mimeType);
- this.playbackState_ = PlaybackState.BUFFERING;
- const uri = mediaItem.media.uri;
- switch (this.playbackType_) {
- case PlaybackType.VIDEO_ELEMENT:
- this.videoElement_.currentTime = startPositionMs / 1000;
- this.shakaPlayer_.unload(false)
- .then(() => {
- this.setMediaElementSrc(uri);
- this.videoElement_.currentTime = startPositionMs / 1000;
- })
- .catch((error) => {
- // Let's still try. We actually don't need Shaka right now.
- this.setMediaElementSrc(uri);
- this.videoElement_.currentTime = startPositionMs / 1000;
- console.error('Shaka error while unloading', error);
- });
- break;
- case PlaybackType.SHAKA_PLAYER:
- this.shakaPlayer_.configure(
- this.configurationFactory_.createConfiguration(
- mediaItem, this.trackSelectionParameters_));
- this.shakaPlayer_.load(uri, startPositionMs / 1000).catch((error) => {
- const shakaError = /** @type {!ShakaError} */ (error);
- if (shakaError.severity !== ShakaError.Severity.RECOVERABLE &&
- shakaError.code !== ShakaError.Code.LOAD_INTERRUPTED) {
- this.fatalShakaError_(shakaError, 'loading failed for uri: ' + uri);
- this.invalidate();
- } else {
- console.error('Recoverable Shaka error while loading', shakaError);
- }
- });
- break;
- default:
- fail('unknown playback type for mime type: ' + mediaItem.mimeType);
- }
-};
-
-/**
- * Sets the uri to the `src` attribute of the media element in a safe way.
- *
- * @param {string} uri The uri to set as the value of the `src` attribute.
- */
-Player.prototype.setMediaElementSrc = function(uri) {
- safedom.setVideoSrc(
- asserts.assertIsHTMLVideoElement(this.videoElement_), uri);
-};
-
-/**
- * Handles a fatal Shaka error by setting the playback error, transitioning to
- * state `IDLE` and setting the playback type to `UNKNOWN`. Player needs to be
- * reprepared after calling this method.
- *
- * @private
- * @param {!ShakaError} shakaError The error.
- * @param {string|undefined} customMessage A custom message.
- */
-Player.prototype.fatalShakaError_ = function(shakaError, customMessage) {
- this.playbackState_ = PlaybackState.IDLE;
- this.playbackType_ = PlaybackType.UNKNOWN;
- this.uuidToPrepare_ = this.queue_[this.windowIndex_] ?
- this.queue_[this.windowIndex_].uuid :
- null;
- if (typeof shakaError.severity === 'undefined') {
- // Not a Shaka error. We need to assume the worst case.
- this.replaceShaka_();
- this.playbackError_ = /** @type {!PlayerError} */ ({
- message: ErrorMessages.UNKNOWN_FATAL_ERROR,
- code: -1,
- category: ErrorCategory.FATAL_SHAKA_ERROR,
- });
- } else {
- // A critical ShakaError. Can be recovered from by calling prepare.
- this.playbackError_ = /** @type {!PlayerError} */ ({
- message: customMessage || shakaError.message ||
- ErrorMessages.SHAKA_UNKNOWN_ERROR,
- code: shakaError.code,
- category: shakaError.category,
- });
- }
- console.error('caught shaka load error', shakaError);
-};
-
-/**
- * Gets the position to use. If `undefined` or `null` is passed as argument the
- * default start position of the media item info of the given windowIndex is
- * returned.
- *
- * @private
- * @param {?number|undefined} positionMs The position in milliseconds,
- * `undefined` or `null`.
- * @param {number} windowIndex The window index for which to evaluate the
- * position.
- * @return {number} The position to use in milliseconds.
- */
-Player.prototype.getPosition_ = function(positionMs, windowIndex) {
- if (positionMs !== undefined) {
- return Math.max(0, positionMs);
- }
- const windowUuid = assert(this.queue_[windowIndex]).uuid;
- const mediaItemInfo =
- this.mediaItemInfoMap_[windowUuid] || DUMMY_MEDIA_ITEM_INFO;
- return mediaItemInfo.defaultStartPositionUs;
-};
-
-/**
- * Refreshes the media item info of the current window.
- *
- * @private
- */
-Player.prototype.updateWindowMediaItemInfo_ = function() {
- this.windowMediaItemInfo_ = this.buildMediaItemInfo_();
- if (this.windowMediaItemInfo_) {
- const mediaItem = this.queue_[this.windowIndex_];
- this.mediaItemInfoMap_[mediaItem.uuid] = this.windowMediaItemInfo_;
- this.evaluateAndSetCurrentPeriod_(this.windowMediaItemInfo_.periods);
- }
-};
-
-/**
- * Evaluates the current period and stores it in a member variable.
- *
- * @private
- * @param {!Array} periods The periods of the current mediaItem.
- */
-Player.prototype.evaluateAndSetCurrentPeriod_ = function(periods) {
- const positionUs = this.getCurrentPositionMs() * 1000;
- let positionInWindowUs = 0;
- periods.some((period, i) => {
- positionInWindowUs += period.durationUs;
- if (positionUs < positionInWindowUs) {
- this.windowPeriodIndex_ = i;
- return true;
- }
- return false;
- });
-};
-
-/**
- * Registers a response filter which is notified when a manifest has been
- * downloaded.
- *
- * @private
- */
-Player.prototype.registerManifestResponseFilter_ = function() {
- if (this.isManifestFilterRegistered_) {
- return;
- }
- this.shakaPlayer_.getNetworkingEngine().registerResponseFilter(
- this.manifestResponseFilter_);
- this.isManifestFilterRegistered_ = true;
-};
-
-/**
- * Unregisters the manifest response filter.
- *
- * @private
- */
-Player.prototype.unregisterManifestResponseFilter_ = function() {
- if (this.isManifestFilterRegistered_) {
- this.shakaPlayer_.getNetworkingEngine().unregisterResponseFilter(
- this.manifestResponseFilter_);
- this.isManifestFilterRegistered_ = false;
- }
-};
-
-/**
- * Builds a MediaItemInfo from the media element.
- *
- * @private
- * @return {!MediaItemInfo} A media item info.
- */
-Player.prototype.buildMediaItemInfoFromElement_ = function() {
- const durationUs = this.videoElement_.duration * 1000 * 1000;
- return /** @type {!MediaItemInfo} */ ({
- isSeekable: !!this.videoElement_.seekable,
- isDynamic: false,
- positionInFirstPeriodUs: 0,
- defaultStartPositionUs: 0,
- windowDurationUs: durationUs,
- periods: [{
- id: 0,
- durationUs: durationUs,
- }],
- });
-};
-
-/**
- * Builds a MediaItemInfo from the manifest or null if no manifest is available.
- *
- * @private
- * @return {!MediaItemInfo}
- */
-Player.prototype.buildMediaItemInfo_ = function() {
- const manifest = this.shakaPlayer_.getManifest();
- if (manifest === null) {
- return DUMMY_MEDIA_ITEM_INFO;
- }
- const timeline = manifest.presentationTimeline;
- const isDynamic = timeline.isLive();
- const windowStartUs = isDynamic ?
- timeline.getSeekRangeStart() * 1000 * 1000 :
- timeline.getSegmentAvailabilityStart() * 1000 * 1000;
- const windowDurationUs = isDynamic ?
- (timeline.getSeekRangeEnd() - timeline.getSeekRangeStart()) * 1000 *
- 1000 :
- timeline.getDuration() * 1000 * 1000;
- const defaultStartPositionUs = isDynamic ?
- timeline.getSeekRangeEnd() * 1000 * 1000 :
- timeline.getSegmentAvailabilityStart() * 1000 * 1000;
-
- const periods = [];
- let previousStartTimeUs = 0;
- let positionInFirstPeriodUs = 0;
- manifest.periods.forEach((period, index) => {
- const startTimeUs = period.startTime * 1000 * 1000;
- periods.push({
- id: Math.floor(startTimeUs),
- });
- if (index > 0) {
- // calculate duration of previous period
- periods[index - 1].durationUs = startTimeUs - previousStartTimeUs;
- if (previousStartTimeUs <= windowStartUs && windowStartUs < startTimeUs) {
- positionInFirstPeriodUs = windowStartUs - previousStartTimeUs;
- }
- }
- previousStartTimeUs = startTimeUs;
- });
- // calculate duration of last period
- if (periods.length) {
- const lastPeriodDurationUs =
- isDynamic ? Infinity : windowDurationUs - previousStartTimeUs;
- periods.slice(-1)[0].durationUs = lastPeriodDurationUs;
- if (previousStartTimeUs <= windowStartUs) {
- positionInFirstPeriodUs = windowStartUs - previousStartTimeUs;
- }
- }
- return /** @type {!MediaItemInfo} */ ({
- windowDurationUs: Math.floor(windowDurationUs),
- defaultStartPositionUs: Math.floor(defaultStartPositionUs),
- isSeekable: this.videoElement_ ? !!this.videoElement_.seekable : false,
- positionInFirstPeriodUs: Math.floor(positionInFirstPeriodUs),
- isDynamic: isDynamic,
- periods: periods,
- });
-};
-
-/**
- * Builds the player state message.
- *
- * @private
- * @return {!PlayerState} The player state.
- */
-Player.prototype.buildPlayerState_ = function() {
- const playerState = {
- playbackState: this.getPlaybackState(),
- playbackParameters: {
- speed: 1,
- pitch: 1,
- skipSilence: false,
- },
- playbackPosition: this.buildPlaybackPosition_(),
- playWhenReady: this.getPlayWhenReady(),
- windowIndex: this.getCurrentWindowIndex(),
- windowCount: this.queue_.length,
- audioTracks: this.getAudioTracks() || [],
- videoTracks: this.getVideoTracks(),
- repeatMode: this.repeatMode_,
- shuffleModeEnabled: this.shuffleModeEnabled_,
- mediaQueue: this.queue_.slice(),
- mediaItemsInfo: this.mediaItemInfoMap_,
- shuffleOrder: this.shuffleOrder_,
- sequenceNumber: -1,
- };
- if (this.playbackError_) {
- playerState.error = this.playbackError_;
- this.playbackError_ = null;
- }
- return playerState;
-};
-
-/**
- * Builds the playback position. Returns null if all properties of the playback
- * position are empty.
- *
- * @private
- * @return {?PlaybackPosition} The playback position.
- */
-Player.prototype.buildPlaybackPosition_ = function() {
- if ((this.playbackState_ === PlaybackState.IDLE && !this.uuidToPrepare_) ||
- this.playbackState_ === PlaybackState.ENDED && this.queue_.length === 0) {
- this.discontinuityReason_ = null;
- return null;
- }
- /** @type {!PlaybackPosition} */
- const playbackPosition = {
- positionMs: this.getCurrentPositionMs(),
- uuid: this.uuidToPrepare_ || this.queue_[this.windowIndex_].uuid,
- periodId: this.windowMediaItemInfo_.periods[this.windowPeriodIndex_].id,
- discontinuityReason: null,
- };
- if (this.discontinuityReason_ !== null) {
- playbackPosition.discontinuityReason = this.discontinuityReason_;
- this.discontinuityReason_ = null;
- }
- return playbackPosition;
-};
-
-exports = Player;
-exports.RepeatMode = RepeatMode;
-exports.PlaybackState = PlaybackState;
-exports.DiscontinuityReason = DiscontinuityReason;
-exports.DUMMY_MEDIA_ITEM_INFO = DUMMY_MEDIA_ITEM_INFO;
diff --git a/cast_receiver_app/src/timeout.js b/cast_receiver_app/src/timeout.js
deleted file mode 100644
index e5df5ec2f4..0000000000
--- a/cast_receiver_app/src/timeout.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-goog.module('exoplayer.cast.Timeout');
-
-/**
- * A timeout which can be cancelled.
- */
-class Timeout {
- constructor() {
- /** @private {?number} */
- this.timeout_ = null;
- }
- /**
- * Returns a promise which resolves when the duration of time defined by
- * delayMs has elapsed and cancel() has not been called earlier.
- *
- * If the timeout is already set, the former timeout is cancelled and a new
- * one is started.
- *
- * @param {number} delayMs The delay after which to resolve or a non-positive
- * value if it should never resolve.
- * @return {!Promise} Resolves after the given delayMs or never
- * for a non-positive delay.
- */
- postDelayed(delayMs) {
- this.cancel();
- return new Promise((resolve, reject) => {
- if (delayMs <= 0) {
- return;
- }
- this.timeout_ = setTimeout(() => {
- if (this.timeout_) {
- this.timeout_ = null;
- resolve();
- }
- }, delayMs);
- });
- }
-
- /** Cancels the timeout. */
- cancel() {
- if (this.timeout_) {
- clearTimeout(this.timeout_);
- this.timeout_ = null;
- }
- }
-
- /** @return {boolean} true if the timeout is currently ongoing. */
- isOngoing() {
- return this.timeout_ !== null;
- }
-}
-
-exports = Timeout;
diff --git a/cast_receiver_app/src/util.js b/cast_receiver_app/src/util.js
deleted file mode 100644
index 75afd9e5d3..0000000000
--- a/cast_receiver_app/src/util.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2018 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.
- */
-
-goog.module('exoplayer.cast.util');
-
-/**
- * Indicates whether the logging is turned on.
- */
-const enableLogging = true;
-
-/**
- * Logs to the console if logging enabled.
- *
- * @param {!Array<*>} statements The log statements to be logged.
- */
-const log = function(statements) {
- if (enableLogging) {
- console.log.apply(console, statements);
- }
-};
-
-/**
- * A comparator function for uuids.
- *
- * @typedef {function(string,string):number}
- */
-let UuidComparator;
-
-/**
- * Creates a comparator function which sorts uuids in descending order by the
- * corresponding index of the given map.
- *
- * @param {!Object} uuidIndexMap The map with uuids as the key
- * and the window index as the value.
- * @return {!UuidComparator} The comparator for sorting.
- */
-const createUuidComparator = function(uuidIndexMap) {
- return (a, b) => {
- const indexA = uuidIndexMap[a] || -1;
- const indexB = uuidIndexMap[b] || -1;
- return indexB - indexA;
- };
-};
-
-exports = {
- log,
- createUuidComparator,
- UuidComparator,
-};
diff --git a/cast_receiver_app/test/caf_bootstrap.js b/cast_receiver_app/test/caf_bootstrap.js
deleted file mode 100644
index 721360e8a7..0000000000
--- a/cast_receiver_app/test/caf_bootstrap.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-/**
- * @fileoverview Declares constants which are provided by the CAF externs and
- * are not included in uncompiled unit tests.
- */
-cast = {
- framework: {
- system: {
- EventType: {
- SENDER_CONNECTED: 'sender_connected',
- SENDER_DISCONNECTED: 'sender_disconnected',
- },
- DisconnectReason: {
- REQUESTED_BY_SENDER: 'requested_by_sender',
- },
- },
- },
-};
diff --git a/cast_receiver_app/test/configuration_factory_test.js b/cast_receiver_app/test/configuration_factory_test.js
deleted file mode 100644
index af9254c59e..0000000000
--- a/cast_receiver_app/test/configuration_factory_test.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-goog.module('exoplayer.cast.test.configurationfactory');
-goog.setTestOnly();
-
-const ConfigurationFactory = goog.require('exoplayer.cast.ConfigurationFactory');
-const testSuite = goog.require('goog.testing.testSuite');
-const util = goog.require('exoplayer.cast.test.util');
-
-let configurationFactory;
-
-testSuite({
- setUp() {
- configurationFactory = new ConfigurationFactory();
- },
-
- /** Tests creating the most basic configuration. */
- testCreateBasicConfiguration() {
- /** @type {!TrackSelectionParameters} */
- const selectionParameters = /** @type {!TrackSelectionParameters} */ ({
- preferredAudioLanguage: 'en',
- preferredTextLanguage: 'it',
- });
- const configuration = configurationFactory.createConfiguration(
- util.queue.slice(0, 1), selectionParameters);
- assertEquals('en', configuration.preferredAudioLanguage);
- assertEquals('it', configuration.preferredTextLanguage);
- // Assert empty drm configuration as default.
- assertArrayEquals(['servers'], Object.keys(configuration.drm));
- assertArrayEquals([], Object.keys(configuration.drm.servers));
- },
-
- /** Tests defaults for undefined audio and text languages. */
- testCreateBasicConfiguration_languagesUndefined() {
- const configuration = configurationFactory.createConfiguration(
- util.queue.slice(0, 1), /** @type {!TrackSelectionParameters} */ ({}));
- assertEquals('', configuration.preferredAudioLanguage);
- assertEquals('', configuration.preferredTextLanguage);
- },
-
- /** Tests creating a drm configuration */
- testCreateDrmConfiguration() {
- /** @type {!MediaItem} */
- const mediaItem = util.queue[1];
- mediaItem.drmSchemes = [
- {
- uuid: 'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed',
- licenseServer: {
- uri: 'drm-uri0',
- },
- },
- {
- uuid: '9a04f079-9840-4286-ab92-e65be0885f95',
- licenseServer: {
- uri: 'drm-uri1',
- },
- },
- {
- uuid: 'unsupported-drm-uuid',
- licenseServer: {
- uri: 'drm-uri2',
- },
- },
- ];
- const configuration =
- configurationFactory.createConfiguration(mediaItem, {});
- assertEquals('drm-uri0', configuration.drm.servers['com.widevine.alpha']);
- assertEquals(
- 'drm-uri1', configuration.drm.servers['com.microsoft.playready']);
- assertEquals(2, Object.entries(configuration.drm.servers).length);
- }
-});
diff --git a/cast_receiver_app/test/externs.js b/cast_receiver_app/test/externs.js
deleted file mode 100644
index a90a367691..0000000000
--- a/cast_receiver_app/test/externs.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-/**
- * Externs for unit tests to avoid renaming of properties.
- *
- * These externs are only required when building with bazel because the
- * closure_js_test compiles tests as well.
- *
- * @externs
- */
-
-/** @record */
-function ValidationObject() {}
-
-/** @type {*} */
-ValidationObject.prototype.field;
-
-/** @record */
-function Uuids() {}
-
-/** @type {!Array} */
-Uuids.prototype.uuids;
diff --git a/cast_receiver_app/test/message_dispatcher_test.js b/cast_receiver_app/test/message_dispatcher_test.js
deleted file mode 100644
index 3e7daaf573..0000000000
--- a/cast_receiver_app/test/message_dispatcher_test.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Copyright (C) 2019 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.
- *
- * @fileoverview Unit tests for the message dispatcher.
- */
-
-goog.module('exoplayer.cast.test.messagedispatcher');
-goog.setTestOnly();
-
-const MessageDispatcher = goog.require('exoplayer.cast.MessageDispatcher');
-const mocks = goog.require('exoplayer.cast.test.mocks');
-const testSuite = goog.require('goog.testing.testSuite');
-
-let contextMock;
-let messageDispatcher;
-
-testSuite({
- setUp() {
- mocks.setUp();
- contextMock = mocks.createCastReceiverContextFake();
- messageDispatcher = new MessageDispatcher(
- 'urn:x-cast:com.google.exoplayer.cast', contextMock);
- },
-
- /** Test marshalling Infinity */
- testStringifyInfinity() {
- const senderId = 'sender0';
- const name = 'Federico Vespucci';
- messageDispatcher.send(senderId, {name: name, duration: Infinity});
-
- const msg = mocks.state().outputMessages[senderId][0];
- assertUndefined(msg.duration);
- assertFalse(msg.hasOwnProperty('duration'));
- assertEquals(name, msg.name);
- assertTrue(msg.hasOwnProperty('name'));
- }
-});
diff --git a/cast_receiver_app/test/mocks.js b/cast_receiver_app/test/mocks.js
deleted file mode 100644
index 244ac72829..0000000000
--- a/cast_receiver_app/test/mocks.js
+++ /dev/null
@@ -1,277 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview Mocks for testing cast components.
- */
-
-goog.module('exoplayer.cast.test.mocks');
-goog.setTestOnly();
-
-const NetworkingEngine = goog.require('shaka.net.NetworkingEngine');
-
-let mockState;
-let manifest;
-
-/**
- * Initializes the state of the mocks. Needs to be called in the setUp method of
- * the unit test.
- */
-const setUp = function() {
- mockState = {
- outputMessages: {},
- listeners: {},
- loadedUri: null,
- preferredTextLanguage: '',
- preferredAudioLanguage: '',
- configuration: null,
- responseFilter: null,
- isSilent: false,
- customMessageListener: undefined,
- mediaElementState: {
- removedAttributes: [],
- },
- manifestState: {
- isLive: false,
- windowDuration: 20,
- startTime: 0,
- delay: 10,
- },
- getManifest: () => manifest,
- setManifest: (m) => {
- manifest = m;
- },
- shakaError: {
- severity: /** CRITICAL */ 2,
- code: /** not 7000 (LOAD_INTERUPTED) */ 3,
- category: /** any */ 1,
- },
- simulateLoad: simulateLoadSuccess,
- /** @type {function(boolean)} */
- setShakaThrowsOnLoad: (doThrow) => {
- mockState.simulateLoad = doThrow ? throwShakaError : simulateLoadSuccess;
- },
- simulateUnload: simulateUnloadSuccess,
- /** @type {function(boolean)} */
- setShakaThrowsOnUnload: (doThrow) => {
- mockState.simulateUnload =
- doThrow ? throwShakaError : simulateUnloadSuccess;
- },
- onSenderConnected: undefined,
- onSenderDisconnected: undefined,
- };
- manifest = {
- periods: [{startTime: mockState.manifestState.startTime}],
- presentationTimeline: {
- getDuration: () => mockState.manifestState.windowDuration,
- isLive: () => mockState.manifestState.isLive,
- getSegmentAvailabilityStart: () => 0,
- getSegmentAvailabilityEnd: () => mockState.manifestState.windowDuration,
- getSeekRangeStart: () => 0,
- getSeekRangeEnd: () => mockState.manifestState.windowDuration -
- mockState.manifestState.delay,
- },
- };
-};
-
-/**
- * Simulates a successful `shakaPlayer.load` call.
- *
- * @param {string} uri The uri to load.
- */
-const simulateLoadSuccess = (uri) => {
- mockState.loadedUri = uri;
- notifyListeners('streaming');
-};
-
-/** Simulates a successful `shakaPlayer.unload` call. */
-const simulateUnloadSuccess = () => {
- mockState.loadedUri = undefined;
- notifyListeners('unloading');
-};
-
-/** @throws {!ShakaError} Thrown in any case. */
-const throwShakaError = () => {
- throw mockState.shakaError;
-};
-
-
-/**
- * Adds a fake event listener.
- *
- * @param {string} type The type of the listener.
- * @param {function(!Object)} listener The callback listener.
- */
-const addEventListener = function(type, listener) {
- mockState.listeners[type] = mockState.listeners[type] || [];
- mockState.listeners[type].push(listener);
-};
-
-/**
- * Notifies the fake listeners of the given type.
- *
- * @param {string} type The type of the listener to notify.
- */
-const notifyListeners = function(type) {
- if (mockState.isSilent || !mockState.listeners[type]) {
- return;
- }
- for (let i = 0; i < mockState.listeners[type].length; i++) {
- mockState.listeners[type][i]({
- type: type
- });
- }
-};
-
-/**
- * Creates an observable for which listeners can be added.
- *
- * @return {!Object} An observable object.
- */
-const createObservable = () => {
- return {
- addEventListener: (type, listener) => {
- addEventListener(type, listener);
- },
- };
-};
-
-/**
- * Creates a fake for the shaka player.
- *
- * @return {!shaka.Player} A shaka player mock object.
- */
-const createShakaFake = () => {
- const shakaFake = /** @type {!shaka.Player} */(createObservable());
- const mediaElement = createMediaElementFake();
- /**
- * @return {!HTMLMediaElement} A media element.
- */
- shakaFake.getMediaElement = () => mediaElement;
- shakaFake.getAudioLanguages = () => [];
- shakaFake.getVariantTracks = () => [];
- shakaFake.configure = (configuration) => {
- mockState.configuration = configuration;
- return true;
- };
- shakaFake.selectTextLanguage = (language) => {
- mockState.preferredTextLanguage = language;
- };
- shakaFake.selectAudioLanguage = (language) => {
- mockState.preferredAudioLanguage = language;
- };
- shakaFake.getManifest = () => manifest;
- shakaFake.unload = async () => mockState.simulateUnload();
- shakaFake.load = async (uri) => mockState.simulateLoad(uri);
- shakaFake.getNetworkingEngine = () => {
- return /** @type {!NetworkingEngine} */ ({
- registerResponseFilter: (responseFilter) => {
- mockState.responseFilter = responseFilter;
- },
- unregisterResponseFilter: (responseFilter) => {
- if (mockState.responseFilter !== responseFilter) {
- throw new Error('unregistering invalid response filter');
- } else {
- mockState.responseFilter = null;
- }
- },
- });
- };
- return shakaFake;
-};
-
-/**
- * Creates a fake for a media element.
- *
- * @return {!HTMLMediaElement} A media element fake.
- */
-const createMediaElementFake = () => {
- const mediaElementFake = /** @type {!HTMLMediaElement} */(createObservable());
- mediaElementFake.load = () => {
- // Do nothing.
- };
- mediaElementFake.play = () => {
- mediaElementFake.paused = false;
- notifyListeners('playing');
- return Promise.resolve();
- };
- mediaElementFake.pause = () => {
- mediaElementFake.paused = true;
- notifyListeners('pause');
- };
- mediaElementFake.seekable = /** @type {!TimeRanges} */({
- length: 1,
- start: (index) => mockState.manifestState.startTime,
- end: (index) => mockState.manifestState.windowDuration,
- });
- mediaElementFake.removeAttribute = (name) => {
- mockState.mediaElementState.removedAttributes.push(name);
- if (name === 'src') {
- mockState.loadedUri = null;
- }
- };
- mediaElementFake.hasAttribute = (name) => {
- return name === 'src' && !!mockState.loadedUri;
- };
- mediaElementFake.buffered = /** @type {!TimeRanges} */ ({
- length: 0,
- start: (index) => null,
- end: (index) => null,
- });
- mediaElementFake.paused = true;
- return mediaElementFake;
-};
-
-/**
- * Creates a cast receiver manager fake.
- *
- * @return {!Object} A cast receiver manager fake.
- */
-const createCastReceiverContextFake = () => {
- return {
- addCustomMessageListener: (namespace, listener) => {
- mockState.customMessageListener = listener;
- },
- sendCustomMessage: (namespace, senderId, message) => {
- mockState.outputMessages[senderId] =
- mockState.outputMessages[senderId] || [];
- mockState.outputMessages[senderId].push(message);
- },
- addEventListener: (eventName, listener) => {
- switch (eventName) {
- case 'sender_connected':
- mockState.onSenderConnected = listener;
- break;
- case 'sender_disconnected':
- mockState.onSenderDisconnected = listener;
- break;
- }
- },
- getSenders: () => [{id: 'sender0'}],
- start: () => {},
- };
-};
-
-/**
- * Returns the state of the mocks.
- *
- * @return {?Object}
- */
-const state = () => mockState;
-
-exports.createCastReceiverContextFake = createCastReceiverContextFake;
-exports.createShakaFake = createShakaFake;
-exports.notifyListeners = notifyListeners;
-exports.setUp = setUp;
-exports.state = state;
diff --git a/cast_receiver_app/test/playback_info_view_test.js b/cast_receiver_app/test/playback_info_view_test.js
deleted file mode 100644
index 87cefe1884..0000000000
--- a/cast_receiver_app/test/playback_info_view_test.js
+++ /dev/null
@@ -1,242 +0,0 @@
-/**
- * Copyright (C) 2019 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.
- *
- * @fileoverview Unit tests for the playback info view.
- */
-
-goog.module('exoplayer.cast.test.PlaybackInfoView');
-goog.setTestOnly();
-
-const PlaybackInfoView = goog.require('exoplayer.cast.PlaybackInfoView');
-const Player = goog.require('exoplayer.cast.Player');
-const testSuite = goog.require('goog.testing.testSuite');
-
-/** The state of the player mock */
-let mockState;
-
-/**
- * Initializes the state of the mock. Needs to be called in the setUp method of
- * the unit test.
- */
-const setUpMockState = function() {
- mockState = {
- playWhenReady: false,
- currentPositionMs: 1000,
- durationMs: 10 * 1000,
- playbackState: 'READY',
- discontinuityReason: undefined,
- listeners: [],
- currentMediaItem: {
- mimeType: 'video/*',
- },
- };
-};
-
-/** Notifies registered listeners with the current player state. */
-const notifyListeners = function() {
- if (!mockState) {
- console.warn(
- 'mock state not initialized. Did you call setUp ' +
- 'when setting up the test case?');
- }
- mockState.listeners.forEach((listener) => {
- listener({
- playWhenReady: mockState.playWhenReady,
- playbackState: mockState.playbackState,
- playbackPosition: {
- currentPositionMs: mockState.currentPositionMs,
- discontinuityReason: mockState.discontinuityReason,
- },
- });
- });
-};
-
-/**
- * Creates a sufficient mock of the Player.
- *
- * @return {!Player}
- */
-const createPlayerMock = function() {
- return /** @type {!Player} */ ({
- addPlayerListener: (listener) => {
- mockState.listeners.push(listener);
- },
- getPlayWhenReady: () => mockState.playWhenReady,
- getPlaybackState: () => mockState.playbackState,
- getCurrentPositionMs: () => mockState.currentPositionMs,
- getDurationMs: () => mockState.durationMs,
- getCurrentMediaItem: () => mockState.currentMediaItem,
- });
-};
-
-/** Inserts the DOM structure the playback info view needs. */
-const insertComponentDom = function() {
- const container = appendChild(document.body, 'div', 'container-id');
- appendChild(container, 'div', 'exo_elapsed_time');
- appendChild(container, 'div', 'exo_elapsed_time_label');
- appendChild(container, 'div', 'exo_duration_label');
-};
-
-/**
- * Creates and appends a child to the parent element.
- *
- * @param {!Element} parent The parent element.
- * @param {string} tagName The tag name of the child element.
- * @param {string} id The id of the child element.
- * @return {!Element} The appended child element.
- */
-const appendChild = function(parent, tagName, id) {
- const child = document.createElement(tagName);
- child.id = id;
- parent.appendChild(child);
- return child;
-};
-
-/** Removes the inserted elements from the DOM again. */
-const removeComponentDom = function() {
- const container = document.getElementById('container-id');
- if (container) {
- container.parentNode.removeChild(container);
- }
-};
-
-let playbackInfoView;
-
-testSuite({
- setUp() {
- insertComponentDom();
- setUpMockState();
- playbackInfoView = new PlaybackInfoView(
- createPlayerMock(), /** containerId= */ 'container-id');
- playbackInfoView.setShowTimeoutMs(1);
- },
-
- tearDown() {
- removeComponentDom();
- },
-
- /** Tests setting the show timeout. */
- testSetShowTimeout() {
- assertEquals(1, playbackInfoView.showTimeoutMs_);
- playbackInfoView.setShowTimeoutMs(10);
- assertEquals(10, playbackInfoView.showTimeoutMs_);
- },
-
- /** Tests rendering the duration to the DOM. */
- testRenderDuration() {
- const el = document.getElementById('exo_duration_label');
- assertEquals('00:10', el.firstChild.firstChild.nodeValue);
- mockState.durationMs = 35 * 1000;
- notifyListeners();
- assertEquals('00:35', el.firstChild.firstChild.nodeValue);
-
- mockState.durationMs =
- (12 * 60 * 60 * 1000) + (20 * 60 * 1000) + (13 * 1000);
- notifyListeners();
- assertEquals('12:20:13', el.firstChild.firstChild.nodeValue);
-
- mockState.durationMs = -1000;
- notifyListeners();
- assertNull(el.nodeValue);
- },
-
- /** Tests rendering the playback position to the DOM. */
- testRenderPlaybackPosition() {
- const el = document.getElementById('exo_elapsed_time_label');
- assertEquals('00:01', el.firstChild.firstChild.nodeValue);
- mockState.currentPositionMs = 2000;
- notifyListeners();
- assertEquals('00:02', el.firstChild.firstChild.nodeValue);
-
- mockState.currentPositionMs =
- (12 * 60 * 60 * 1000) + (20 * 60 * 1000) + (13 * 1000);
- notifyListeners();
- assertEquals('12:20:13', el.firstChild.firstChild.nodeValue);
-
- mockState.currentPositionMs = -1000;
- notifyListeners();
- assertNull(el.nodeValue);
-
- mockState.currentPositionMs = 0;
- notifyListeners();
- assertEquals('00:00', el.firstChild.firstChild.nodeValue);
- },
-
- /** Tests rendering the timebar width reflects position and duration. */
- testRenderTimebar() {
- const el = document.getElementById('exo_elapsed_time');
- assertEquals('10%', el.style.width);
-
- mockState.currentPositionMs = 0;
- notifyListeners();
- assertEquals('0px', el.style.width);
-
- mockState.currentPositionMs = 5 * 1000;
- notifyListeners();
- assertEquals('50%', el.style.width);
-
- mockState.currentPositionMs = mockState.durationMs * 2;
- notifyListeners();
- assertEquals('100%', el.style.width);
-
- mockState.currentPositionMs = -1;
- notifyListeners();
- assertEquals('0px', el.style.width);
- },
-
- /** Tests whether the update timeout is set and removed. */
- testUpdateTimeout_setAndRemoved() {
- assertFalse(playbackInfoView.updateTimeout_.isOngoing());
-
- mockState.playWhenReady = true;
- notifyListeners();
- assertTrue(playbackInfoView.updateTimeout_.isOngoing());
-
- mockState.playWhenReady = false;
- notifyListeners();
- assertFalse(playbackInfoView.updateTimeout_.isOngoing());
- },
-
- /** Tests whether the show timeout is set when playback starts. */
- testHideTimeout_setAndRemoved() {
- assertFalse(playbackInfoView.hideTimeout_.isOngoing());
-
- mockState.playWhenReady = true;
- notifyListeners();
- assertNotUndefined(playbackInfoView.hideTimeout_);
- assertTrue(playbackInfoView.hideTimeout_.isOngoing());
-
- mockState.playWhenReady = false;
- notifyListeners();
- assertFalse(playbackInfoView.hideTimeout_.isOngoing());
- },
-
- /** Test whether the view switches to always on for audio media. */
- testAlwaysOnForAudio() {
- playbackInfoView.setShowTimeoutMs(50);
- assertEquals(50, playbackInfoView.showTimeoutMs_);
- // The player transitions from video to audio stream.
- mockState.discontinuityReason = 'PERIOD_TRANSITION';
- mockState.currentMediaItem.mimeType = 'audio/*';
- notifyListeners();
- assertEquals(0, playbackInfoView.showTimeoutMs_);
-
- mockState.discontinuityReason = 'PERIOD_TRANSITION';
- mockState.currentMediaItem.mimeType = 'video/*';
- notifyListeners();
- assertEquals(50, playbackInfoView.showTimeoutMs_);
- },
-
-});
diff --git a/cast_receiver_app/test/player_test.js b/cast_receiver_app/test/player_test.js
deleted file mode 100644
index 96dfbf8614..0000000000
--- a/cast_receiver_app/test/player_test.js
+++ /dev/null
@@ -1,470 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview Unit tests for playback methods.
- */
-
-goog.module('exoplayer.cast.test');
-goog.setTestOnly();
-
-const ConfigurationFactory = goog.require('exoplayer.cast.ConfigurationFactory');
-const Player = goog.require('exoplayer.cast.Player');
-const mocks = goog.require('exoplayer.cast.test.mocks');
-const testSuite = goog.require('goog.testing.testSuite');
-const util = goog.require('exoplayer.cast.test.util');
-
-let player;
-let shakaFake;
-
-testSuite({
- setUp() {
- mocks.setUp();
- shakaFake = mocks.createShakaFake();
- player = new Player(shakaFake, new ConfigurationFactory());
- },
-
- /** Tests the player initialisation */
- testPlayerInitialisation() {
- mocks.state().isSilent = true;
- const states = [];
- let stateCounter = 0;
- let currentState;
- player.addPlayerListener((playerState) => {
- states.push(playerState);
- });
-
- // Dump the initial state manually.
- player.invalidate();
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- assertEquals(0, currentState.mediaQueue.length);
- assertEquals(0, currentState.windowIndex);
- assertNull(currentState.playbackPosition);
-
- // Seek with uuid to prepare with later
- const uuid = 'uuid1';
- player.seekToUuid(uuid, 30 * 1000);
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- assertEquals(30 * 1000, player.getCurrentPositionMs());
- assertEquals(0, player.getCurrentWindowIndex());
- assertEquals(-1, player.windowIndex_);
- assertEquals(1, currentState.playbackPosition.periodId);
- assertEquals(uuid, currentState.playbackPosition.uuid);
- assertEquals(uuid, player.uuidToPrepare_);
-
- // Add a DASH media item.
- player.addQueueItems(0, util.queue.slice(0, 2));
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- assertEquals('IDLE', currentState.playbackState);
- assertNotNull(currentState.playbackPosition);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, currentState.mediaQueue);
-
- // Prepare.
- player.prepare();
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- assertEquals(2, currentState.mediaQueue.length);
- assertEquals('BUFFERING', currentState.playbackState);
- assertEquals(
- Player.DUMMY_MEDIA_ITEM_INFO, currentState.mediaItemsInfo[uuid]);
- assertNull(player.uuidToPrepare_);
-
- // The video element starts waiting.
- mocks.state().isSilent = false;
- mocks.notifyListeners('waiting');
- // Nothing happens, masked buffering state after preparing.
- assertEquals(stateCounter, states.length);
-
- // The manifest arrives.
- mocks.notifyListeners('streaming');
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- assertEquals(2, currentState.mediaQueue.length);
- assertEquals('BUFFERING', currentState.playbackState);
- assertEquals(uuid, currentState.playbackPosition.uuid);
- assertEquals(0, currentState.playbackPosition.periodId);
- assertEquals(30 * 1000, currentState.playbackPosition.positionMs);
- // The dummy media item info has been replaced by the real one.
- assertEquals(20000000, currentState.mediaItemsInfo[uuid].windowDurationUs);
- assertEquals(0, currentState.mediaItemsInfo[uuid].defaultStartPositionUs);
- assertEquals(0, currentState.mediaItemsInfo[uuid].positionInFirstPeriodUs);
- assertTrue(currentState.mediaItemsInfo[uuid].isSeekable);
- assertFalse(currentState.mediaItemsInfo[uuid].isDynamic);
-
- // Tracks have initially changed.
- mocks.notifyListeners('trackschanged');
- // Nothing happens because the media item info remains the same.
- assertEquals(stateCounter, states.length);
-
- // The video element reports the first frame rendered.
- mocks.notifyListeners('loadeddata');
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- assertEquals(2, currentState.mediaQueue.length);
- assertEquals('READY', currentState.playbackState);
- assertEquals(uuid, currentState.playbackPosition.uuid);
- assertEquals(0, currentState.playbackPosition.periodId);
- assertEquals(30 * 1000, currentState.playbackPosition.positionMs);
-
- // Playback starts.
- mocks.notifyListeners('playing');
- // Nothing happens; we are ready already.
- assertEquals(stateCounter, states.length);
-
- // Add another queue item.
- player.addQueueItems(1, util.queue.slice(3, 4));
- stateCounter++;
- assertEquals(stateCounter, states.length);
- mocks.state().isSilent = true;
- // Seek to the next queue item.
- player.seekToWindow(1, 0);
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- const uuid1 = currentState.mediaQueue[1].uuid;
- assertEquals(
- Player.DUMMY_MEDIA_ITEM_INFO, currentState.mediaItemsInfo[uuid1]);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, currentState.mediaQueue);
-
- // The video element starts waiting.
- mocks.state().isSilent = false;
- mocks.notifyListeners('waiting');
- // Nothing happens, masked buffering state after preparing.
- assertEquals(stateCounter, states.length);
-
- // The manifest arrives.
- mocks.notifyListeners('streaming');
- stateCounter++;
- assertEquals(stateCounter, states.length);
- currentState = states[stateCounter - 1];
- // The dummy media item info has been replaced by the real one.
- assertEquals(20000000, currentState.mediaItemsInfo[uuid].windowDurationUs);
- assertEquals(0, currentState.mediaItemsInfo[uuid].defaultStartPositionUs);
- assertEquals(0, currentState.mediaItemsInfo[uuid].positionInFirstPeriodUs);
- assertTrue(currentState.mediaItemsInfo[uuid].isSeekable);
- assertFalse(currentState.mediaItemsInfo[uuid].isDynamic);
- },
-
- /** Tests next and previous window when not yet prepared. */
- testNextPreviousWindow_notPrepared() {
- assertEquals(-1, player.getNextWindowIndex());
- assertEquals(-1, player.getPreviousWindowIndex());
- player.addQueueItems(0, util.queue.slice(0, 2));
- assertEquals(-1, player.getNextWindowIndex());
- assertEquals(-1, player.getPreviousWindowIndex());
- },
-
- /** Tests setting play when ready. */
- testPlayWhenReady() {
- player.addQueueItems(0, util.queue.slice(0, 3));
- let playWhenReady = false;
- player.addPlayerListener((state) => {
- playWhenReady = state.playWhenReady;
- });
-
- assertEquals(false, player.getPlayWhenReady());
- assertEquals(false, playWhenReady);
-
- player.setPlayWhenReady(true);
- assertEquals(true, player.getPlayWhenReady());
- assertEquals(true, playWhenReady);
-
- player.setPlayWhenReady(false);
- assertEquals(false, player.getPlayWhenReady());
- assertEquals(false, playWhenReady);
- },
-
- /** Tests seeking to another position in the actual window. */
- async testSeek_inWindow() {
- player.addQueueItems(0, util.queue.slice(0, 3));
- await player.seekToWindow(0, 1000);
-
- assertEquals(1, shakaFake.getMediaElement().currentTime);
- assertEquals(1000, player.getCurrentPositionMs());
- assertEquals(0, player.getCurrentWindowIndex());
- },
-
- /** Tests seeking to another window. */
- async testSeek_nextWindow() {
- player.addQueueItems(0, util.queue.slice(0, 3));
- await player.prepare();
- assertEquals(util.queue[0].media.uri, shakaFake.getMediaElement().src);
- assertEquals(-1, player.getPreviousWindowIndex());
- assertEquals(1, player.getNextWindowIndex());
-
- player.seekToWindow(1, 2000);
- assertEquals(0, player.getPreviousWindowIndex());
- assertEquals(2, player.getNextWindowIndex());
- assertEquals(2000, player.getCurrentPositionMs());
- assertEquals(1, player.getCurrentWindowIndex());
- assertEquals(util.queue[1].media.uri, mocks.state().loadedUri);
- },
-
- /** Tests the repeat mode 'none' */
- testRepeatMode_none() {
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- assertEquals(Player.RepeatMode.OFF, player.getRepeatMode());
- assertEquals(-1, player.getPreviousWindowIndex());
- assertEquals(1, player.getNextWindowIndex());
-
- player.seekToWindow(2, 0);
- assertEquals(1, player.getPreviousWindowIndex());
- assertEquals(-1, player.getNextWindowIndex());
- },
-
- /** Tests the repeat mode 'all'. */
- testRepeatMode_all() {
- let repeatMode;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- player.addPlayerListener((state) => {
- repeatMode = state.repeatMode;
- });
- player.setRepeatMode(Player.RepeatMode.ALL);
- assertEquals(Player.RepeatMode.ALL, repeatMode);
-
- player.seekToWindow(0,0);
- assertEquals(2, player.getPreviousWindowIndex());
- assertEquals(1, player.getNextWindowIndex());
-
- player.seekToWindow(2, 0);
- assertEquals(1, player.getPreviousWindowIndex());
- assertEquals(0, player.getNextWindowIndex());
- },
-
- /**
- * Tests navigation within the queue when repeat mode and shuffle mode is on.
- */
- testRepeatMode_all_inShuffleMode() {
- const initialOrder = [2, 1, 0];
- let shuffleOrder;
- let windowIndex;
- player.addQueueItems(0, util.queue.slice(0, 3), initialOrder);
- player.prepare();
- player.addPlayerListener((state) => {
- shuffleOrder = state.shuffleOrder;
- windowIndex = state.windowIndex;
- });
- player.setRepeatMode(Player.RepeatMode.ALL);
- player.setShuffleModeEnabled(true);
- assertEquals(windowIndex, player.shuffleOrder_[player.shuffleIndex_]);
- assertArrayEquals(initialOrder, shuffleOrder);
-
- player.seekToWindow(shuffleOrder[2], 0);
- assertEquals(shuffleOrder[2], windowIndex);
- assertEquals(shuffleOrder[0], player.getNextWindowIndex());
- assertEquals(shuffleOrder[1], player.getPreviousWindowIndex());
-
- player.seekToWindow(shuffleOrder[0], 0);
- assertEquals(shuffleOrder[0], windowIndex);
- },
-
- /** Tests the repeat mode 'one' */
- testRepeatMode_one() {
- let repeatMode;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- player.addPlayerListener((state) => {
- repeatMode = state.repeatMode;
- });
- player.setRepeatMode(Player.RepeatMode.ONE);
- assertEquals(Player.RepeatMode.ONE, repeatMode);
- assertEquals(0, player.getPreviousWindowIndex());
- assertEquals(0, player.getNextWindowIndex());
-
- player.seekToWindow(1, 0);
- assertEquals(1, player.getPreviousWindowIndex());
- assertEquals(1, player.getNextWindowIndex());
-
- player.setShuffleModeEnabled(true);
- assertEquals(1, player.getPreviousWindowIndex());
- assertEquals(1, player.getNextWindowIndex());
- },
-
- /** Tests building a media item info from the manifest. */
- testBuildMediaItemInfo_fromManifest() {
- let mediaItemInfos = null;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.addPlayerListener((state) => {
- mediaItemInfos = state.mediaItemsInfo;
- });
- player.seekToWindow(1, 0);
- player.prepare();
- assertUndefined(mediaItemInfos['uuid0']);
- const mediaItemInfo = mediaItemInfos['uuid1'];
- assertNotUndefined(mediaItemInfo);
- assertFalse(mediaItemInfo.isDynamic);
- assertTrue(mediaItemInfo.isSeekable);
- assertEquals(0, mediaItemInfo.defaultStartPositionUs);
- assertEquals(20 * 1000 * 1000, mediaItemInfo.windowDurationUs);
- assertEquals(1, mediaItemInfo.periods.length);
- assertEquals(20 * 1000 * 1000, mediaItemInfo.periods[0].durationUs);
- },
-
- /** Tests building a media item info with multiple periods. */
- testBuildMediaItemInfo_fromManifest_multiPeriod() {
- let mediaItemInfos = null;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.addPlayerListener((state) => {
- mediaItemInfos = state.mediaItemsInfo;
- });
- // Setting manifest properties to emulate a multiperiod stream manifest.
- mocks.state().getManifest().periods.push({startTime: 20});
- mocks.state().manifestState.windowDuration = 50;
- player.seekToWindow(1, 0);
- player.prepare();
-
- const mediaItemInfo = mediaItemInfos['uuid1'];
- assertNotUndefined(mediaItemInfo);
- assertFalse(mediaItemInfo.isDynamic);
- assertTrue(mediaItemInfo.isSeekable);
- assertEquals(0, mediaItemInfo.defaultStartPositionUs);
- assertEquals(50 * 1000 * 1000, mediaItemInfo.windowDurationUs);
- assertEquals(2, mediaItemInfo.periods.length);
- assertEquals(20 * 1000 * 1000, mediaItemInfo.periods[0].durationUs);
- assertEquals(30 * 1000 * 1000, mediaItemInfo.periods[1].durationUs);
- },
-
- /** Tests building a media item info from a live manifest. */
- testBuildMediaItemInfo_fromManifest_live() {
- let mediaItemInfos = null;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.addPlayerListener((state) => {
- mediaItemInfos = state.mediaItemsInfo;
- });
- // Setting manifest properties to emulate a live stream manifest.
- mocks.state().manifestState.isLive = true;
- mocks.state().manifestState.windowDuration = 30;
- mocks.state().manifestState.delay = 10;
- mocks.state().getManifest().periods.push({startTime: 20});
- player.seekToWindow(1, 0);
- player.prepare();
-
- const mediaItemInfo = mediaItemInfos['uuid1'];
- assertNotUndefined(mediaItemInfo);
- assertTrue(mediaItemInfo.isDynamic);
- assertTrue(mediaItemInfo.isSeekable);
- assertEquals(20 * 1000 * 1000, mediaItemInfo.defaultStartPositionUs);
- assertEquals(20 * 1000 * 1000, mediaItemInfo.windowDurationUs);
- assertEquals(2, mediaItemInfo.periods.length);
- assertEquals(20 * 1000 * 1000, mediaItemInfo.periods[0].durationUs);
- assertEquals(Infinity, mediaItemInfo.periods[1].durationUs);
- },
-
- /** Tests whether the shaka request filter is set for life streams. */
- testRequestFilterIsSetAndRemovedForLive() {
- player.addQueueItems(0, util.queue.slice(0, 3));
-
- // Set manifest properties to emulate a live stream manifest.
- mocks.state().manifestState.isLive = true;
- mocks.state().manifestState.windowDuration = 30;
- mocks.state().manifestState.delay = 10;
- mocks.state().getManifest().periods.push({startTime: 20});
-
- assertNull(mocks.state().responseFilter);
- assertFalse(player.isManifestFilterRegistered_);
- player.seekToWindow(1, 0);
- player.prepare();
- assertNotNull(mocks.state().responseFilter);
- assertTrue(player.isManifestFilterRegistered_);
-
- // Set manifest properties to emulate a non-live stream */
- mocks.state().manifestState.isLive = false;
- mocks.state().manifestState.windowDuration = 20;
- mocks.state().manifestState.delay = 0;
- mocks.state().getManifest().periods.push({startTime: 20});
-
- player.seekToWindow(0, 0);
- assertNull(mocks.state().responseFilter);
- assertFalse(player.isManifestFilterRegistered_);
- },
-
- /** Tests whether the media info is removed when queue item is removed. */
- testRemoveMediaItemInfo() {
- let mediaItemInfos = null;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.addPlayerListener((state) => {
- mediaItemInfos = state.mediaItemsInfo;
- });
- player.seekToWindow(1, 0);
- player.prepare();
- assertNotUndefined(mediaItemInfos['uuid1']);
- player.removeQueueItems(['uuid1']);
- assertUndefined(mediaItemInfos['uuid1']);
- },
-
- /** Tests shuffling. */
- testSetShuffeModeEnabled() {
- let shuffleModeEnabled = false;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.addPlayerListener((state) => {
- shuffleModeEnabled = state.shuffleModeEnabled;
- });
- player.setShuffleModeEnabled(true);
- assertTrue(shuffleModeEnabled);
-
- player.setShuffleModeEnabled(false);
- assertFalse(shuffleModeEnabled);
- },
-
- /** Tests setting a new playback order. */
- async testSetShuffleOrder() {
- const defaultOrder = [0, 1, 2];
- let shuffleOrder;
- player.addPlayerListener((state) => {
- shuffleOrder = state.shuffleOrder;
- });
- await player.addQueueItems(0, util.queue.slice(0, 3), defaultOrder);
- assertArrayEquals(defaultOrder, shuffleOrder);
-
- player.setShuffleOrder_([2, 1, 0]);
- assertArrayEquals([2, 1, 0], player.shuffleOrder_);
- },
-
- /** Tests setting a new playback order with incorrect length. */
- async testSetShuffleOrder_incorrectLength() {
- const defaultOrder = [0, 1, 2];
- let shuffleOrder;
- player.addPlayerListener((state) => {
- shuffleOrder = state.shuffleOrder;
- });
- await player.addQueueItems(0, util.queue.slice(0, 3), defaultOrder);
- assertArrayEquals(defaultOrder, shuffleOrder);
-
- shuffleOrder = undefined;
- player.setShuffleOrder_([2, 1]);
- assertUndefined(shuffleOrder);
- },
-
- /** Tests falling into ENDED when prepared with empty queue. */
- testPrepare_withEmptyQueue() {
- player.seekToUuid('uuid1000', 1000);
- assertEquals('uuid1000', player.uuidToPrepare_);
- player.prepare();
- assertEquals('ENDED', player.getPlaybackState());
- assertNull(player.uuidToPrepare_);
- player.seekToUuid('uuid1000', 1000);
- assertNull(player.uuidToPrepare_);
- },
-});
diff --git a/cast_receiver_app/test/queue_test.js b/cast_receiver_app/test/queue_test.js
deleted file mode 100644
index b46361fb2e..0000000000
--- a/cast_receiver_app/test/queue_test.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview Unit tests for queue manipulations.
- */
-
-goog.module('exoplayer.cast.test.queue');
-goog.setTestOnly();
-
-const ConfigurationFactory = goog.require('exoplayer.cast.ConfigurationFactory');
-const Player = goog.require('exoplayer.cast.Player');
-const mocks = goog.require('exoplayer.cast.test.mocks');
-const testSuite = goog.require('goog.testing.testSuite');
-const util = goog.require('exoplayer.cast.test.util');
-
-let player;
-
-testSuite({
- setUp() {
- mocks.setUp();
- player = new Player(mocks.createShakaFake(), new ConfigurationFactory());
- },
-
- /** Tests adding queue items. */
- testAddQueueItem() {
- let queue = [];
- player.addPlayerListener((state) => {
- queue = state.mediaQueue;
- });
- assertEquals(0, queue.length);
- player.addQueueItems(0, util.queue.slice(0, 3));
- assertEquals(util.queue[0].media.uri, queue[0].media.uri);
- assertEquals(util.queue[1].media.uri, queue[1].media.uri);
- assertEquals(util.queue[2].media.uri, queue[2].media.uri);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests that duplicate queue items are ignored. */
- testAddDuplicateQueueItem() {
- let queue = [];
- player.addPlayerListener((state) => {
- queue = state.mediaQueue;
- });
- assertEquals(0, queue.length);
- // Insert three items.
- player.addQueueItems(0, util.queue.slice(0, 3));
- // Insert two of which the first is a duplicate.
- player.addQueueItems(1, util.queue.slice(2, 4));
- assertEquals(4, queue.length);
- assertArrayEquals(
- ['uuid0', 'uuid3', 'uuid1', 'uuid2'], queue.slice().map((i) => i.uuid));
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests moving queue items. */
- testMoveQueueItem() {
- const shuffleOrder = [0, 2, 1];
- let queue = [];
- player.addPlayerListener((state) => {
- queue = state.mediaQueue;
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.moveQueueItem('uuid0', 1, shuffleOrder);
- assertEquals(util.queue[1].media.uri, queue[0].media.uri);
- assertEquals(util.queue[0].media.uri, queue[1].media.uri);
- assertEquals(util.queue[2].media.uri, queue[2].media.uri);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
-
- queue = undefined;
- // invalid to index
- player.moveQueueItem('uuid0', 11, [0, 1, 2]);
- assertTrue(typeof queue === 'undefined');
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
- // negative to index
- player.moveQueueItem('uuid0', -11, shuffleOrder);
- assertTrue(typeof queue === 'undefined');
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
- // unknown uuid
- player.moveQueueItem('unknown', 1, shuffleOrder);
- assertTrue(typeof queue === 'undefined');
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
- },
-
- /** Tests removing queue items. */
- testRemoveQueueItems() {
- let queue = [];
- player.addPlayerListener((state) => {
- queue = state.mediaQueue;
- });
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- player.prepare();
- player.seekToWindow(1, 0);
- assertEquals(1, player.getCurrentWindowIndex());
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
-
- // Remove the first item.
- player.removeQueueItems(['uuid0']);
- assertEquals(2, queue.length);
- assertEquals(util.queue[1].media.uri, queue[0].media.uri);
- assertEquals(util.queue[2].media.uri, queue[1].media.uri);
- assertEquals(0, player.getCurrentWindowIndex());
- assertArrayEquals([1,0], player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
-
- // Calling stop without reseting preserves the queue.
- player.stop(false);
- assertEquals('uuid1', player.uuidToPrepare_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
-
- // Remove the item at the end of the queue.
- player.removeQueueItems(['uuid2']);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
-
- // Remove the last remaining item in the queue.
- player.removeQueueItems(['uuid1']);
- assertEquals(0, queue.length);
- assertEquals('IDLE', player.getPlaybackState());
- assertEquals(0, player.getCurrentWindowIndex());
- assertArrayEquals([], player.shuffleOrder_);
- assertNull(player.uuidToPrepare_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, player.queue_);
- },
-
- /** Tests removing multiple unordered queue items at once. */
- testRemoveQueueItems_multiple() {
- let queue = [];
- player.addPlayerListener((state) => {
- queue = state.mediaQueue;
- });
- player.addQueueItems(0, util.queue.slice(0, 6), []);
- player.prepare();
-
- assertEquals(6, queue.length);
- player.removeQueueItems(['uuid1', 'uuid5', 'uuid3']);
- assertArrayEquals(['uuid0', 'uuid2', 'uuid4'], queue.map((i) => i.uuid));
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests whether stopping with reset=true resets queue and uuidToIndexMap */
- testStop_resetTrue() {
- let queue = [];
- player.addPlayerListener((state) => {
- queue = state.mediaQueue;
- });
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- player.prepare();
- player.stop(true);
- assertEquals(0, player.queue_.length);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-});
diff --git a/cast_receiver_app/test/receiver_test.js b/cast_receiver_app/test/receiver_test.js
deleted file mode 100644
index 303a1caf64..0000000000
--- a/cast_receiver_app/test/receiver_test.js
+++ /dev/null
@@ -1,1027 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview Unit tests for receiver.
- */
-
-goog.module('exoplayer.cast.test.receiver');
-goog.setTestOnly();
-
-const ConfigurationFactory = goog.require('exoplayer.cast.ConfigurationFactory');
-const MessageDispatcher = goog.require('exoplayer.cast.MessageDispatcher');
-const Player = goog.require('exoplayer.cast.Player');
-const Receiver = goog.require('exoplayer.cast.Receiver');
-const mocks = goog.require('exoplayer.cast.test.mocks');
-const testSuite = goog.require('goog.testing.testSuite');
-const util = goog.require('exoplayer.cast.test.util');
-
-/** @type {?Player|undefined} */
-let player;
-/** @type {!Array} */
-let queue = [];
-let shakaFake;
-let castContextMock;
-
-/**
- * Sends a message to the receiver under test.
- *
- * @param {!Object} message The message to send as json.
- */
-const sendMessage = function(message) {
- mocks.state().customMessageListener({
- data: message,
- senderId: 'sender0',
- });
-};
-
-/**
- * Creates a valid media item with the suffix appended to each field.
- *
- * @param {string} suffix The suffix to append to the fields value.
- * @return {!Object} The media item.
- */
-const createMediaItem = function(suffix) {
- return {
- uuid: 'uuid' + suffix,
- media: {uri: 'uri' + suffix},
- mimeType: 'application/dash+xml',
- };
-};
-
-let messageSequence = 0;
-
-/**
- * Creates a message in the format sent bey the sender app.
- *
- * @param {string} method The name of the method.
- * @param {?Object} args The arguments.
- * @return {!Object} The message.
- */
-const createMessage = function (method, args) {
- return {
- method: method,
- args: args,
- sequenceNumber: ++messageSequence,
- };
-};
-
-/**
- * Asserts the `playerState` is in the same state as just after creation of the
- * player.
- *
- * @param {!PlayerState} playerState The player state to assert.
- * @param {string} playbackState The expected playback state.
- */
-const assertInitialState = function(playerState, playbackState) {
- assertEquals(playbackState, playerState.playbackState);
- // Assert the state is in initial state.
- assertArrayEquals([], queue);
- assertEquals(0, playerState.windowCount);
- assertEquals(0, playerState.windowIndex);
- assertUndefined(playerState.playbackError);
- assertNull(playerState.playbackPosition);
- // Assert player properties.
- assertEquals(0, player.getDurationMs());
- assertArrayEquals([], Object.entries(player.mediaItemInfoMap_));
- assertEquals(0, player.windowPeriodIndex_);
- assertEquals(999, player.playbackType_);
- assertEquals(0, player.getCurrentWindowIndex());
- assertEquals(Player.DUMMY_MEDIA_ITEM_INFO, player.windowMediaItemInfo_);
-};
-
-
-testSuite({
- setUp() {
- mocks.setUp();
- shakaFake = mocks.createShakaFake();
- castContextMock = mocks.createCastReceiverContextFake();
- player = new Player(shakaFake, new ConfigurationFactory());
- player.addPlayerListener((playerState) => {
- queue = playerState.mediaQueue;
- });
- const messageDispatcher = new MessageDispatcher(
- 'urn:x-cast:com.google.exoplayer.cast', castContextMock);
- new Receiver(player, castContextMock, messageDispatcher);
- },
-
- tearDown() {
- queue = [];
- },
-
- /** Tests whether a status was sent to the sender on connect. */
- testNotifyClientConnected() {
- assertUndefined(mocks.state().outputMessages['sender0']);
-
- sendMessage(createMessage('player.onClientConnected', {}));
- const message = mocks.state().outputMessages['sender0'][0];
- assertEquals(messageSequence, message.sequenceNumber);
- },
-
- /**
- * Tests whether a custom message listener has been registered after
- * construction.
- */
- testCustomMessageListener() {
- assertTrue(goog.isFunction(mocks.state().customMessageListener));
- },
-
- /** Tests set playWhenReady. */
- testSetPlayWhenReady() {
- let playWhenReady;
- player.addPlayerListener((playerState) => {
- playWhenReady = playerState.playWhenReady;
- });
-
- sendMessage(createMessage(
- 'player.setPlayWhenReady',
- { playWhenReady: true }
- ));
- assertTrue(playWhenReady);
- sendMessage(createMessage(
- 'player.setPlayWhenReady',
- { playWhenReady: false }
- ));
- assertFalse(playWhenReady);
- },
-
- /** Tests setting repeat modes. */
- testSetRepeatMode() {
- let repeatMode;
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- player.addPlayerListener((playerState) => {
- repeatMode = playerState.repeatMode;
- });
-
- sendMessage(createMessage(
- 'player.setRepeatMode',
- { repeatMode: Player.RepeatMode.ONE }
- ));
- assertEquals(Player.RepeatMode.ONE, repeatMode);
- assertEquals(0, player.getNextWindowIndex());
- assertEquals(0, player.getPreviousWindowIndex());
-
- sendMessage(createMessage(
- 'player.setRepeatMode',
- { repeatMode: Player.RepeatMode.ALL }
- ));
- assertEquals(Player.RepeatMode.ALL, repeatMode);
- assertEquals(1, player.getNextWindowIndex());
- assertEquals(2, player.getPreviousWindowIndex());
-
- sendMessage(createMessage(
- 'player.setRepeatMode',
- { repeatMode: Player.RepeatMode.OFF }
- ));
- assertEquals(Player.RepeatMode.OFF, repeatMode);
- assertEquals(1, player.getNextWindowIndex());
- assertTrue(player.getPreviousWindowIndex() < 0);
- },
-
- /** Tests setting an invalid repeat mode value. */
- testSetRepeatMode_invalid_noStateChange() {
- let repeatMode;
- player.addPlayerListener((playerState) => {
- repeatMode = playerState.repeatMode;
- });
-
- sendMessage(createMessage(
- 'player.setRepeatMode',
- { repeatMode: "UNKNOWN" }
- ));
- assertEquals(Player.RepeatMode.OFF, player.repeatMode_);
- assertUndefined(repeatMode);
- player.invalidate();
- assertEquals(Player.RepeatMode.OFF, repeatMode);
- },
-
- /** Tests enabling and disabling shuffle mode. */
- testSetShuffleModeEnabled() {
- const enableMessage = createMessage('player.setShuffleModeEnabled', {
- shuffleModeEnabled: true,
- });
- const disableMessage = createMessage('player.setShuffleModeEnabled', {
- shuffleModeEnabled: false,
- });
- let shuffleModeEnabled;
- player.addPlayerListener((state) => {
- shuffleModeEnabled = state.shuffleModeEnabled;
- });
- assertFalse(player.shuffleModeEnabled_);
- sendMessage(enableMessage);
- assertTrue(shuffleModeEnabled);
- sendMessage(disableMessage);
- assertFalse(shuffleModeEnabled);
- },
-
- /** Tests adding a single media item to the queue. */
- testAddMediaItem_single() {
- const suffix = '0';
- const jsonMessage = createMessage('player.addItems', {
- index: 0,
- items: [
- createMediaItem(suffix),
- ],
- shuffleOrder: [0],
- });
-
- sendMessage(jsonMessage);
- assertEquals(1, queue.length);
- assertEquals('uuid0', queue[0].uuid);
- assertEquals('uri0', queue[0].media.uri);
- assertArrayEquals([0], player.shuffleOrder_);
- },
-
- /** Tests adding multiple media items to the queue. */
- testAddMediaItem_multiple() {
- const shuffleOrder = [0, 2, 1];
- const jsonMessage = createMessage('player.addItems', {
- index: 0,
- items: [
- createMediaItem('0'),
- createMediaItem('1'),
- createMediaItem('2'),
- ],
- shuffleOrder: shuffleOrder,
- });
-
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2'], queue.map((x) => x.uuid));
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- },
-
- /** Tests adding a media item to end of the queue by omitting the index. */
- testAddMediaItem_noindex_addstoend() {
- const shuffleOrder = [1, 3, 2, 0];
- const jsonMessage = createMessage('player.addItems', {
- items: [createMediaItem('99')],
- shuffleOrder: shuffleOrder,
- });
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- let queue = [];
- player.addPlayerListener((playerState) => {
- queue = playerState.mediaQueue;
- });
- sendMessage(jsonMessage);
- assertEquals(4, queue.length);
- assertEquals('uuid99', queue[3].uuid);
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- },
-
- /** Tests adding items with a shuffle order of invalid length. */
- testAddMediaItems_invalidShuffleOrderLength() {
- const shuffleOrder = [1, 3, 2];
- const jsonMessage = createMessage('player.addItems', {
- items: [createMediaItem('99')],
- shuffleOrder: shuffleOrder,
- });
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- let queue = [];
- player.addPlayerListener((playerState) => {
- queue = playerState.mediaQueue;
- });
- sendMessage(jsonMessage);
- assertEquals(4, queue.length);
- assertEquals('uuid99', queue[3].uuid);
- assertEquals(4, player.shuffleOrder_.length);
- },
-
- /** Tests inserting a media item to the queue. */
- testAddMediaItem_insert() {
- const index = 1;
- const shuffleOrder = [1, 0, 3, 2, 4];
- const firstInsertionMessage = createMessage('player.addItems', {
- index,
- items: [
- createMediaItem('99'),
- createMediaItem('100'),
- ],
- shuffleOrder,
- });
- const prepareMessage = createMessage('player.prepare', {});
- const secondInsertionMessage = createMessage('player.addItems', {
- index,
- items: [
- createMediaItem('199'),
- createMediaItem('1100'),
- ],
- shuffleOrder,
- });
- // fill with three items
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- player.seekToUuid('uuid99', 0);
-
- sendMessage(firstInsertionMessage);
- // The window index does not change when IDLE.
- assertEquals(1, player.getCurrentWindowIndex());
- assertEquals(5, queue.length);
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
-
- // Prepare sets the index by the uuid to which we seeked.
- sendMessage(prepareMessage);
- assertEquals(1, player.getCurrentWindowIndex());
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- // Add two items at the current window index.
- sendMessage(secondInsertionMessage);
- // Current window index is adjusted.
- assertEquals(3, player.getCurrentWindowIndex());
- assertEquals(7, queue.length);
- assertEquals('uuid199', queue[index].uuid);
- assertEquals(7, player.shuffleOrder_.length);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests adding a media item with an index larger than the queue size. */
- testAddMediaItem_indexLargerThanQueueSize_addsToEnd() {
- const index = 4;
- const jsonMessage = createMessage('player.addItems', {
- index: index,
- items: [
- createMediaItem('99'),
- createMediaItem('100'),
- ],
- shuffleOrder: [],
- });
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
-
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2'], queue.map((x) => x.uuid));
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2', 'uuid99', 'uuid100'],
- queue.map((x) => x.uuid));
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests removing an item from the queue. */
- testRemoveMediaItem() {
- const jsonMessage =
- createMessage('player.removeItems', {uuids: ['uuid1', 'uuid0']});
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2'], queue.map((x) => x.uuid));
-
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid2'], queue.map((x) => x.uuid));
- assertArrayEquals([0], player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests removing the currently playing item from the queue. */
- async testRemoveMediaItem_currentItem() {
- const jsonMessage =
- createMessage('player.removeItems', {uuids: ['uuid1', 'uuid0']});
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- player.seekToWindow(1, 0);
- player.prepare();
-
- await sendMessage(jsonMessage);
- assertArrayEquals(['uuid2'], queue.map((x) => x.uuid));
- assertEquals(0, player.getCurrentWindowIndex());
- assertEquals(util.queue[2].media.uri, shakaFake.getMediaElement().src);
- assertArrayEquals([0], player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests removing items which affect the current window index. */
- async testRemoveMediaItem_affectsWindowIndex() {
- const jsonMessage =
- createMessage('player.removeItems', {uuids: ['uuid1', 'uuid0']});
- const currentUri = util.queue[4].media.uri;
- player.addQueueItems(0, util.queue.slice(0, 6), [3, 2, 1, 4, 0, 5]);
- player.prepare();
- await player.seekToWindow(4, 2000);
- assertEquals(currentUri, shakaFake.getMediaElement().src);
-
- sendMessage(jsonMessage);
- assertEquals(4, queue.length);
- assertEquals('uuid4', queue[player.getCurrentWindowIndex()].uuid);
- assertEquals(2, player.getCurrentWindowIndex());
- assertEquals(currentUri, shakaFake.getMediaElement().src);
- assertArrayEquals([1, 0, 2, 3], player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests removing the last item of the queue. */
- testRemoveMediaItem_firstItem_windowIndexIsCorrect() {
- const jsonMessage =
- createMessage('player.removeItems', {uuids: ['uuid0']});
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- player.seekToWindow(1, 0);
-
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid1', 'uuid2'], queue.map((x) => x.uuid));
- assertEquals(0, player.getCurrentWindowIndex());
- assertArrayEquals([1, 0], player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests removing the last item of the queue. */
- testRemoveMediaItem_lastItem_windowIndexIsCorrect() {
- const jsonMessage =
- createMessage('player.removeItems', {uuids: ['uuid2']});
- player.addQueueItems(0, util.queue.slice(0, 3), [0, 2, 1]);
- player.seekToWindow(2, 0);
- player.prepare();
-
- mocks.state().isSilent = true;
- const states = [];
- player.addPlayerListener((playerState) => {
- states.push(playerState);
- });
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid0', 'uuid1'], queue.map((x) => x.uuid));
- assertEquals(1, player.getCurrentWindowIndex());
- assertArrayEquals([0, 1], player.shuffleOrder_);
- assertEquals(1, states.length);
- assertEquals(Player.PlaybackState.BUFFERING, states[0].playbackState);
- assertEquals(
- Player.DiscontinuityReason.PERIOD_TRANSITION,
- states[0].playbackPosition.discontinuityReason);
- assertEquals(
- Player.DUMMY_MEDIA_ITEM_INFO, states[0].mediaItemsInfo['uuid1']);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests removing items all items. */
- testRemoveMediaItem_removeAll() {
- const jsonMessage = createMessage('player.removeItems',
- {uuids: ['uuid1', 'uuid0', 'uuid2']});
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.seekToWindow(2, 2000);
- player.prepare();
- let playerState;
- player.addPlayerListener((state) => {
- playerState = state;
- });
-
- sendMessage(jsonMessage);
- assertInitialState(playerState, 'ENDED');
- assertEquals(0, player.getCurrentWindowIndex());
- assertArrayEquals([], player.shuffleOrder_);
- assertEquals(Player.PlaybackState.ENDED, player.getPlaybackState());
- util.assertUuidIndexMap(player.queueUuidIndexMap_, []);
- },
-
- /** Tests moving an item in the queue. */
- testMoveItem() {
- let shuffleOrder = [0, 2, 1];
- const jsonMessage = createMessage('player.moveItem', {
- uuid: 'uuid2',
- index: 0,
- shuffleOrder: shuffleOrder,
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
-
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2'], queue.map((x) => x.uuid));
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid2', 'uuid0', 'uuid1'], queue.map((x) => x.uuid));
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests moving the currently playing item in the queue. */
- testMoveItem_currentWindowIndex() {
- let shuffleOrder = [0, 2, 1];
- const jsonMessage = createMessage('player.moveItem', {
- uuid: 'uuid2',
- index: 0,
- shuffleOrder: shuffleOrder,
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- player.seekToUuid('uuid2', 0);
- assertEquals(2, player.getCurrentWindowIndex());
-
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2'], queue.map((x) => x.uuid));
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid2', 'uuid0', 'uuid1'], queue.map((x) => x.uuid));
- assertEquals(0, player.getCurrentWindowIndex());
- assertArrayEquals(shuffleOrder, player.shuffleOrder_);
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests moving an item from before to after the currently playing item. */
- testMoveItem_decreaseCurrentWindowIndex() {
- const jsonMessage = createMessage('player.moveItem', {
- uuid: 'uuid0',
- index: 5,
- shuffleOrder: [],
- });
- player.addQueueItems(0, util.queue.slice(0, 6));
- player.prepare();
- player.seekToWindow(2, 0);
- assertEquals(2, player.getCurrentWindowIndex());
-
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2', 'uuid3', 'uuid4', 'uuid5'],
- queue.map((x) => x.uuid));
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid1', 'uuid2', 'uuid3', 'uuid4', 'uuid5', 'uuid0'],
- queue.map((x) => x.uuid));
- assertEquals(1, player.getCurrentWindowIndex());
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests moving an item from after to before the currently playing item. */
- testMoveItem_increaseCurrentWindowIndex() {
- const jsonMessage = createMessage('player.moveItem', {
- uuid: 'uuid5',
- index: 0,
- shuffleOrder: [],
- });
- player.addQueueItems(0, util.queue.slice(0, 6));
- player.prepare();
- player.seekToWindow(2, 0);
- assertEquals(2, player.getCurrentWindowIndex());
-
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2', 'uuid3', 'uuid4', 'uuid5'],
- queue.map((x) => x.uuid));
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid5', 'uuid0', 'uuid1', 'uuid2', 'uuid3', 'uuid4'],
- queue.map((x) => x.uuid));
- assertEquals(3, player.getCurrentWindowIndex());
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests moving an item from after to the current window index. */
- testMoveItem_toCurrentWindowIndex_fromAfter() {
- const jsonMessage = createMessage('player.moveItem', {
- uuid: 'uuid5',
- index: 2,
- shuffleOrder: [],
- });
- player.addQueueItems(0, util.queue.slice(0, 6));
- player.prepare();
- player.seekToWindow(2, 0);
- assertEquals(2, player.getCurrentWindowIndex());
-
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2', 'uuid3', 'uuid4', 'uuid5'],
- queue.map((x) => x.uuid));
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid0', 'uuid1', 'uuid5', 'uuid2', 'uuid3', 'uuid4'],
- queue.map((x) => x.uuid));
- assertEquals(3, player.getCurrentWindowIndex());
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests moving an item from before to the current window index. */
- testMoveItem_toCurrentWindowIndex_fromBefore() {
- const jsonMessage = createMessage('player.moveItem', {
- uuid: 'uuid0',
- index: 2,
- shuffleOrder: [],
- });
- player.addQueueItems(0, util.queue.slice(0, 6));
- player.prepare();
- player.seekToWindow(2, 0);
- assertEquals(2, player.getCurrentWindowIndex());
-
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2', 'uuid3', 'uuid4', 'uuid5'],
- queue.map((x) => x.uuid));
- sendMessage(jsonMessage);
- assertArrayEquals(['uuid1', 'uuid2', 'uuid0', 'uuid3', 'uuid4', 'uuid5'],
- queue.map((x) => x.uuid));
- assertEquals(1, player.getCurrentWindowIndex());
- util.assertUuidIndexMap(player.queueUuidIndexMap_, queue);
- },
-
- /** Tests seekTo. */
- testSeekTo() {
- const jsonMessage = createMessage('player.seekTo',
- {
- 'uuid': 'uuid1',
- 'positionMs': 2000
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- sendMessage(jsonMessage);
- assertEquals(2000, player.getCurrentPositionMs());
- assertEquals(1, player.getCurrentWindowIndex());
- },
-
- /** Tests seekTo to unknown uuid. */
- testSeekTo_unknownUuid() {
- const jsonMessage = createMessage('player.seekTo',
- {
- 'uuid': 'unknown',
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- player.seekToWindow(1, 2000);
- assertEquals(2000, player.getCurrentPositionMs());
- assertEquals(1, player.getCurrentWindowIndex());
-
- sendMessage(jsonMessage);
- assertEquals(2000, player.getCurrentPositionMs());
- assertEquals(1, player.getCurrentWindowIndex());
- },
-
- /** Tests seekTo without position. */
- testSeekTo_noPosition_defaultsToZero() {
- const jsonMessage = createMessage('player.seekTo',
- {
- 'uuid': 'uuid1',
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- sendMessage(jsonMessage);
- assertEquals(0, player.getCurrentPositionMs());
- assertEquals(1, player.getCurrentWindowIndex());
- },
-
- /** Tests seekTo to negative position. */
- testSeekTo_negativePosition_defaultsToZero() {
- const jsonMessage = createMessage('player.seekTo',
- {
- 'uuid': 'uuid2',
- 'positionMs': -1,
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- player.seekToWindow(1, 2000);
- assertEquals(2000, player.getCurrentPositionMs());
- assertEquals(1, player.getCurrentWindowIndex());
-
- sendMessage(jsonMessage);
- assertEquals(0, player.getCurrentPositionMs());
- assertEquals(2, player.getCurrentWindowIndex());
- },
-
- /** Tests whether validation is turned on. */
- testMediaItemValidation_isOn() {
- const index = 0;
- const mediaItem = createMediaItem('99');
- delete mediaItem.uuid;
- const jsonMessage = createMessage('player.addItems', {
- index: index,
- items: [mediaItem],
- shuffleOrder: [],
- });
-
- sendMessage(jsonMessage);
- assertEquals(0, queue.length);
- },
-
- /** Tests whether the state is sent to sender apps on state transition. */
- testPlayerStateIsSent_withCorrectSequenceNumber() {
- assertUndefined(mocks.state().outputMessages['sender0']);
- const playMessage =
- createMessage('player.setPlayWhenReady', {playWhenReady: true});
- sendMessage(playMessage);
-
- const playerState = mocks.state().outputMessages['sender0'][0];
- assertTrue(playerState.playWhenReady);
- assertEquals(playMessage.sequenceNumber, playerState.sequenceNumber);
- },
-
- /** Tests whether a connect of a sender app sends the current player state. */
- testSenderConnection() {
- const onSenderConnected = mocks.state().onSenderConnected;
- assertTrue(goog.isFunction(onSenderConnected));
- onSenderConnected({senderId: 'sender0'});
-
- const playerState = mocks.state().outputMessages['sender0'][0];
- assertEquals(Player.RepeatMode.OFF, playerState.repeatMode);
- assertEquals('IDLE', playerState.playbackState);
- assertArrayEquals([], playerState.mediaQueue);
- assertEquals(-1, playerState.sequenceNumber);
- },
-
- /** Tests whether a disconnect of a sender notifies the message dispatcher. */
- testSenderDisconnection_callsMessageDispatcher() {
- mocks.setUp();
- let notifiedSenderId;
- const myPlayer = new Player(mocks.createShakaFake());
- const myManagerFake = mocks.createCastReceiverContextFake();
- new Receiver(myPlayer, myManagerFake, {
- registerActionHandler() {},
- notifySenderDisconnected(senderId) {
- notifiedSenderId = senderId;
- },
- });
-
- const onSenderDisconnected = mocks.state().onSenderDisconnected;
- assertTrue(goog.isFunction(onSenderDisconnected));
- onSenderDisconnected({senderId: 'sender0'});
- assertEquals('sender0', notifiedSenderId);
- },
-
- /**
- * Tests whether the state right after creation of the player matches
- * expectations.
- */
- testInitialState() {
- mocks.state().isSilent = true;
- let playerState;
- player.addPlayerListener((state) => {
- playerState = state;
- });
- assertEquals(0, player.getCurrentPositionMs());
- // Dump a player state to the listener.
- player.invalidate();
- // Asserts the state just after creation.
- assertInitialState(playerState, 'IDLE');
- },
-
- /** Tests whether user properties can be changed when in IDLE state */
- testChangingUserPropertiesWhenIdle() {
- mocks.state().isSilent = true;
- const states = [];
- let counter = 0;
- player.addPlayerListener((state) => {
- states.push(state);
- });
- // Adding items when IDLE.
- player.addQueueItems(0, util.queue.slice(0, 3));
- counter++;
- assertEquals(counter, states.length);
- assertEquals(Player.PlaybackState.IDLE, states[counter - 1].playbackState);
- assertArrayEquals(
- ['uuid0', 'uuid1', 'uuid2'],
- states[counter - 1].mediaQueue.map((i) => i.uuid));
-
- // Set playWhenReady when IDLE.
- assertFalse(player.getPlayWhenReady());
- player.setPlayWhenReady(true);
- counter++;
- assertTrue(player.getPlayWhenReady());
- assertEquals(counter, states.length);
- assertEquals(Player.PlaybackState.IDLE, states[counter - 1].playbackState);
-
- // Seeking when IDLE.
- player.seekToUuid('uuid2', 1000);
- counter++;
- // Window index not set when idle.
- assertEquals(2, player.getCurrentWindowIndex());
- assertEquals(1000, player.getCurrentPositionMs());
- assertEquals(counter, states.length);
- assertEquals(Player.PlaybackState.IDLE, states[counter - 1].playbackState);
- // But window index is set when prepared.
- player.prepare();
- assertEquals(2, player.getCurrentWindowIndex());
- },
-
- /** Tests the state after calling prepare. */
- testPrepare() {
- mocks.state().isSilent = true;
- const states = [];
- let counter = 0;
- player.addPlayerListener((state) => {
- states.push(state);
- });
- const prepareMessage = createMessage('player.prepare', {});
-
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.seekToWindow(1, 1000);
- counter += 2;
-
- // Sends prepare message.
- sendMessage(prepareMessage);
- counter++;
- assertEquals(counter, states.length);
- assertEquals('uuid1', states[counter - 1].playbackPosition.uuid);
- assertEquals(
- Player.PlaybackState.BUFFERING, states[counter - 1].playbackState);
-
- // Fakes Shaka events.
- mocks.state().isSilent = false;
- mocks.notifyListeners('streaming');
- mocks.notifyListeners('loadeddata');
- counter += 2;
- assertEquals(counter, states.length);
- assertEquals(Player.PlaybackState.READY, states[counter - 1].playbackState);
- },
-
- /** Tests stopping the player with `reset=true`. */
- testStop_resetTrue() {
- mocks.state().isSilent = true;
- let playerState;
- player.addPlayerListener((state) => {
- playerState = state;
- });
- const stopMessage = createMessage('player.stop', {reset: true});
-
- player.setRepeatMode(Player.RepeatMode.ALL);
- player.setShuffleModeEnabled(true);
- player.setPlayWhenReady(true);
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- mocks.state().isSilent = false;
- mocks.notifyListeners('loadeddata');
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2'], queue.map((i) => i.uuid));
- assertEquals(0, playerState.windowIndex);
- assertNotEquals(Player.DUMMY_MEDIA_ITEM_INFO, player.windowMediaItemInfo_);
- assertEquals(1, player.playbackType_);
- // Stop the player.
- sendMessage(stopMessage);
- // Asserts the state looks the same as just after creation.
- assertInitialState(playerState, 'IDLE');
- assertNull(playerState.playbackPosition);
- // Assert player properties are preserved.
- assertTrue(playerState.shuffleModeEnabled);
- assertTrue(playerState.playWhenReady);
- assertEquals(Player.RepeatMode.ALL, playerState.repeatMode);
- },
-
- /** Tests stopping the player with `reset=false`. */
- testStop_resetFalse() {
- mocks.state().isSilent = true;
- let playerState;
- player.addPlayerListener((state) => {
- playerState = state;
- });
- const stopMessage = createMessage('player.stop', {reset: false});
-
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- player.seekToUuid('uuid1', 1000);
- mocks.state().isSilent = false;
- mocks.notifyListeners('streaming');
- mocks.notifyListeners('trackschanged');
- mocks.notifyListeners('loadeddata');
- assertArrayEquals(['uuid0', 'uuid1', 'uuid2'], queue.map((i) => i.uuid));
- assertEquals(1, playerState.windowIndex);
- assertNotEquals(Player.DUMMY_MEDIA_ITEM_INFO, player.windowMediaItemInfo_);
- assertEquals(2, player.playbackType_);
- // Stop the player.
- sendMessage(stopMessage);
- assertEquals('IDLE', playerState.playbackState);
- assertUndefined(playerState.playbackError);
- // Assert the timeline is preserved.
- assertEquals(3, queue.length);
- assertEquals(3, playerState.windowCount);
- assertEquals(1, player.windowIndex_);
- assertEquals(1, playerState.windowIndex);
- // Assert the playback position is correct.
- assertEquals(1000, playerState.playbackPosition.positionMs);
- assertEquals('uuid1', playerState.playbackPosition.uuid);
- assertEquals(0, playerState.playbackPosition.periodId);
- assertNull(playerState.playbackPosition.discontinuityReason);
- assertEquals(1000, player.getCurrentPositionMs());
- // Assert player properties are preserved.
- assertEquals(20000, player.getDurationMs());
- assertEquals(2, Object.entries(player.mediaItemInfoMap_).length);
- assertEquals(0, player.windowPeriodIndex_);
- assertEquals(1, player.getCurrentWindowIndex());
- assertEquals(1, player.windowIndex_);
- assertNotEquals(Player.DUMMY_MEDIA_ITEM_INFO, player.windowMediaItemInfo_);
- assertEquals(999, player.playbackType_);
- assertEquals('uuid1', player.uuidToPrepare_);
- },
-
- /**
- * Tests the state after having removed the last item in the queue. This
- * resolves to the same state like calling `stop(true)` except that the state
- * is ENDED and the queue is naturally empty and hence the windowIndex is
- * unset.
- */
- testRemoveLastQueueItem() {
- mocks.state().isSilent = true;
- let playerState;
- player.addPlayerListener((state) => {
- playerState = state;
- });
- const removeAllItemsMessage = createMessage(
- 'player.removeItems', {uuids: ['uuid0', 'uuid1', 'uuid2']});
-
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.seekToWindow(0, 1000);
- player.prepare();
- mocks.state().isSilent = false;
- mocks.notifyListeners('loadeddata');
- // Remove all items.
- sendMessage(removeAllItemsMessage);
- // Assert the state after removal of all items.
- assertInitialState(playerState, 'ENDED');
- },
-
- /** Tests whether a player state is sent when no item is added. */
- testAddItem_noop() {
- mocks.state().isSilent = true;
- let playerStates = [];
- player.addPlayerListener((state) => {
- playerStates.push(state);
- });
- const noOpMessage = createMessage('player.addItems', {
- index: 0,
- items: [
- util.queue[0],
- ],
- shuffleOrder: [0],
- });
- player.addQueueItems(0, [util.queue[0]], []);
- player.prepare();
- assertEquals(2, playerStates.length);
- assertEquals(2, mocks.state().outputMessages['sender0'].length);
- sendMessage(noOpMessage);
- assertEquals(2, playerStates.length);
- assertEquals(3, mocks.state().outputMessages['sender0'].length);
- },
-
- /** Tests whether a player state is sent when no item is removed. */
- testRemoveItem_noop() {
- mocks.state().isSilent = true;
- let playerStates = [];
- player.addPlayerListener((state) => {
- playerStates.push(state);
- });
- const noOpMessage =
- createMessage('player.removeItems', {uuids: ['uuid00']});
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- assertEquals(2, playerStates.length);
- assertEquals(2, mocks.state().outputMessages['sender0'].length);
- sendMessage(noOpMessage);
- assertEquals(2, playerStates.length);
- assertEquals(3, mocks.state().outputMessages['sender0'].length);
- },
-
- /** Tests whether a player state is sent when item is not moved. */
- testMoveItem_noop() {
- mocks.state().isSilent = true;
- let playerStates = [];
- player.addPlayerListener((state) => {
- playerStates.push(state);
- });
- const noOpMessage = createMessage('player.moveItem', {
- uuid: 'uuid00',
- index: 0,
- shuffleOrder: [],
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
- assertEquals(2, playerStates.length);
- assertEquals(2, mocks.state().outputMessages['sender0'].length);
- sendMessage(noOpMessage);
- assertEquals(2, playerStates.length);
- assertEquals(3, mocks.state().outputMessages['sender0'].length);
- },
-
- /** Tests whether playback actions send a state when no-op */
- testNoOpPlaybackActionsSendPlayerState() {
- mocks.state().isSilent = true;
- let playerStates = [];
- let parsedMessage;
- player.addPlayerListener((state) => {
- playerStates.push(state);
- });
- player.addQueueItems(0, util.queue.slice(0, 3));
- player.prepare();
-
- const outputMessages = mocks.state().outputMessages['sender0'];
- const setupMessageCount = playerStates.length;
- let totalMessageCount = setupMessageCount;
- assertEquals(setupMessageCount, playerStates.length);
- assertEquals(totalMessageCount, outputMessages.length);
-
- const firstNoOpMessage = createMessage('player.setPlayWhenReady', {
- playWhenReady: false,
- });
- let expectedSequenceNumber = firstNoOpMessage.sequenceNumber;
-
- sendMessage(firstNoOpMessage);
- totalMessageCount++;
- assertEquals(setupMessageCount, playerStates.length);
- assertEquals(totalMessageCount, outputMessages.length);
- parsedMessage = outputMessages[totalMessageCount - 1];
- assertEquals(expectedSequenceNumber++, parsedMessage.sequenceNumber);
-
- sendMessage(createMessage('player.setRepeatMode', {
- repeatMode: 'OFF',
- }));
- totalMessageCount++;
- assertEquals(setupMessageCount, playerStates.length);
- assertEquals(totalMessageCount, outputMessages.length);
- parsedMessage = outputMessages[totalMessageCount - 1];
- assertEquals(expectedSequenceNumber++, parsedMessage.sequenceNumber);
-
- sendMessage(createMessage('player.setShuffleModeEnabled', {
- shuffleModeEnabled: false,
- }));
- totalMessageCount++;
- assertEquals(setupMessageCount, playerStates.length);
- assertEquals(totalMessageCount, outputMessages.length);
- parsedMessage = outputMessages[totalMessageCount - 1];
- assertEquals(expectedSequenceNumber++, parsedMessage.sequenceNumber);
-
- sendMessage(createMessage('player.seekTo', {
- uuid: 'not_existing',
- positionMs: 0,
- }));
- totalMessageCount++;
- assertEquals(setupMessageCount, playerStates.length);
- assertEquals(totalMessageCount, outputMessages.length);
- parsedMessage = outputMessages[totalMessageCount - 1];
- assertEquals(expectedSequenceNumber++, parsedMessage.sequenceNumber);
- },
-});
diff --git a/cast_receiver_app/test/shaka_error_handling_test.js b/cast_receiver_app/test/shaka_error_handling_test.js
deleted file mode 100644
index a7dafd3176..0000000000
--- a/cast_receiver_app/test/shaka_error_handling_test.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview Unit tests for playback methods.
- */
-
-goog.module('exoplayer.cast.test.shaka');
-goog.setTestOnly();
-
-const ConfigurationFactory = goog.require('exoplayer.cast.ConfigurationFactory');
-const Player = goog.require('exoplayer.cast.Player');
-const mocks = goog.require('exoplayer.cast.test.mocks');
-const testSuite = goog.require('goog.testing.testSuite');
-const util = goog.require('exoplayer.cast.test.util');
-
-let player;
-let shakaFake;
-
-testSuite({
- setUp() {
- mocks.setUp();
- shakaFake = mocks.createShakaFake();
- player = new Player(shakaFake, new ConfigurationFactory());
- },
-
- /** Tests Shaka critical error handling on load. */
- async testShakaCriticalError_onload() {
- mocks.state().isSilent = true;
- mocks.state().setShakaThrowsOnLoad(true);
- let playerState;
- player.addPlayerListener((state) => {
- playerState = state;
- });
- player.addQueueItems(0, util.queue.slice(0, 2));
- player.seekToUuid('uuid1', 2000);
- player.setPlayWhenReady(true);
- // Calling prepare triggers a critical Shaka error.
- await player.prepare();
- // Assert player state after error.
- assertEquals('IDLE', playerState.playbackState);
- assertEquals(mocks.state().shakaError.category, playerState.error.category);
- assertEquals(mocks.state().shakaError.code, playerState.error.code);
- assertEquals(
- 'loading failed for uri: http://example1.com',
- playerState.error.message);
- assertEquals(999, player.playbackType_);
- // Assert player properties are preserved.
- assertEquals(2000, player.getCurrentPositionMs());
- assertTrue(player.getPlayWhenReady());
- assertEquals(1, player.getCurrentWindowIndex());
- assertEquals(1, player.windowIndex_);
- },
-
- /** Tests Shaka critical error handling on unload. */
- async testShakaCriticalError_onunload() {
- mocks.state().isSilent = true;
- mocks.state().setShakaThrowsOnUnload(true);
- let playerState;
- player.addPlayerListener((state) => {
- playerState = state;
- });
- player.addQueueItems(0, util.queue.slice(0, 2));
- player.setPlayWhenReady(true);
- assertUndefined(player.videoElement_.src);
- // Calling prepare triggers a critical Shaka error.
- await player.prepare();
- // Assert player state after caught and ignored error.
- await assertEquals('BUFFERING', playerState.playbackState);
- assertEquals('http://example.com', player.videoElement_.src);
- assertEquals(1, player.playbackType_);
- },
-});
diff --git a/cast_receiver_app/test/util.js b/cast_receiver_app/test/util.js
deleted file mode 100644
index 22244675b7..0000000000
--- a/cast_receiver_app/test/util.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview Description of this file.
- */
-
-goog.module('exoplayer.cast.test.util');
-goog.setTestOnly();
-
-/**
- * The queue of sample media items
- *
- * @type {!Array}
- */
-const queue = [
- {
- uuid: 'uuid0',
- media: {
- uri: 'http://example.com',
- },
- mimeType: 'video/*',
- },
- {
- uuid: 'uuid1',
- media: {
- uri: 'http://example1.com',
- },
- mimeType: 'application/dash+xml',
- },
- {
- uuid: 'uuid2',
- media: {
- uri: 'http://example2.com',
- },
- mimeType: 'video/*',
- },
- {
- uuid: 'uuid3',
- media: {
- uri: 'http://example3.com',
- },
- mimeType: 'application/dash+xml',
- },
- {
- uuid: 'uuid4',
- media: {
- uri: 'http://example4.com',
- },
- mimeType: 'video/*',
- },
- {
- uuid: 'uuid5',
- media: {
- uri: 'http://example5.com',
- },
- mimeType: 'application/dash+xml',
- },
-];
-
-/**
- * Asserts whether the map of uuids is complete and points to the correct
- * indices.
- *
- * @param {!Object} uuidIndexMap The uuid to index map.
- * @param {!Array} queue The media item queue.
- */
-const assertUuidIndexMap = (uuidIndexMap, queue) => {
- assertEquals(queue.length, Object.entries(uuidIndexMap).length);
- queue.forEach((mediaItem, index) => {
- assertEquals(uuidIndexMap[mediaItem.uuid], index);
- });
-};
-
-exports.queue = queue;
-exports.assertUuidIndexMap = assertUuidIndexMap;
diff --git a/cast_receiver_app/test/validation_test.js b/cast_receiver_app/test/validation_test.js
deleted file mode 100644
index 8e58185cfa..0000000000
--- a/cast_receiver_app/test/validation_test.js
+++ /dev/null
@@ -1,266 +0,0 @@
-/**
- * Copyright (C) 2018 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.
- *
- * @fileoverview Unit tests for queue manipulations.
- */
-
-goog.module('exoplayer.cast.test.validation');
-goog.setTestOnly();
-
-const testSuite = goog.require('goog.testing.testSuite');
-const validation = goog.require('exoplayer.cast.validation');
-
-/**
- * Creates a sample drm media for validation tests.
- *
- * @return {!Object} A dummy media item with a drm scheme.
- */
-const createDrmMedia = function() {
- return {
- uuid: 'string',
- media: {
- uri: 'string',
- },
- mimeType: 'application/dash+xml',
- drmSchemes: [
- {
- uuid: 'string',
- licenseServer: {
- uri: 'string',
- requestHeaders: {
- 'string': 'string',
- },
- },
- },
- ],
- };
-};
-
-testSuite({
-
- /** Tests minimal valid media item. */
- testValidateMediaItem_minimal() {
- const mediaItem = {
- uuid: 'string',
- media: {
- uri: 'string',
- },
- mimeType: 'application/dash+xml',
- };
- assertTrue(validation.validateMediaItem(mediaItem));
-
- const uuid = mediaItem.uuid;
- delete mediaItem.uuid;
- assertFalse(validation.validateMediaItem(mediaItem));
- mediaItem.uuid = uuid;
- assertTrue(validation.validateMediaItem(mediaItem));
-
- const mimeType = mediaItem.mimeType;
- delete mediaItem.mimeType;
- assertFalse(validation.validateMediaItem(mediaItem));
- mediaItem.mimeType = mimeType;
- assertTrue(validation.validateMediaItem(mediaItem));
-
- const media = mediaItem.media;
- delete mediaItem.media;
- assertFalse(validation.validateMediaItem(mediaItem));
- mediaItem.media = media;
- assertTrue(validation.validateMediaItem(mediaItem));
-
- const uri = mediaItem.media.uri;
- delete mediaItem.media.uri;
- assertFalse(validation.validateMediaItem(mediaItem));
- mediaItem.media.uri = uri;
- assertTrue(validation.validateMediaItem(mediaItem));
- },
-
- /** Tests media item drm property validation. */
- testValidateMediaItem_drmSchemes() {
- const mediaItem = createDrmMedia();
- assertTrue(validation.validateMediaItem(mediaItem));
-
- const uuid = mediaItem.drmSchemes[0].uuid;
- delete mediaItem.drmSchemes[0].uuid;
- assertFalse(validation.validateMediaItem(mediaItem));
- mediaItem.drmSchemes[0].uuid = uuid;
- assertTrue(validation.validateMediaItem(mediaItem));
-
- const licenseServer = mediaItem.drmSchemes[0].licenseServer;
- delete mediaItem.drmSchemes[0].licenseServer;
- assertFalse(validation.validateMediaItem(mediaItem));
- mediaItem.drmSchemes[0].licenseServer = licenseServer;
- assertTrue(validation.validateMediaItem(mediaItem));
-
- const uri = mediaItem.drmSchemes[0].licenseServer.uri;
- delete mediaItem.drmSchemes[0].licenseServer.uri;
- assertFalse(validation.validateMediaItem(mediaItem));
- mediaItem.drmSchemes[0].licenseServer.uri = uri;
- assertTrue(validation.validateMediaItem(mediaItem));
- },
-
- /** Tests validation of startPositionUs and endPositionUs. */
- testValidateMediaItem_endAndStartPositionUs() {
- const mediaItem = createDrmMedia();
-
- mediaItem.endPositionUs = 0;
- mediaItem.startPositionUs = 120 * 1000;
- assertTrue(validation.validateMediaItem(mediaItem));
-
- mediaItem.endPositionUs = '0';
- assertFalse(validation.validateMediaItem(mediaItem));
-
- mediaItem.endPositionUs = 0;
- assertTrue(validation.validateMediaItem(mediaItem));
-
- mediaItem.startPositionUs = true;
- assertFalse(validation.validateMediaItem(mediaItem));
- },
-
- /** Tests validation of the title. */
- testValidateMediaItem_title() {
- const mediaItem = createDrmMedia();
-
- mediaItem.title = '0';
- assertTrue(validation.validateMediaItem(mediaItem));
-
- mediaItem.title = 0;
- assertFalse(validation.validateMediaItem(mediaItem));
- },
-
- /** Tests validation of the description. */
- testValidateMediaItem_description() {
- const mediaItem = createDrmMedia();
-
- mediaItem.description = '0';
- assertTrue(validation.validateMediaItem(mediaItem));
-
- mediaItem.description = 0;
- assertFalse(validation.validateMediaItem(mediaItem));
- },
-
- /** Tests validating property of type string. */
- testValidateProperty_string() {
- const obj = {
- field: 'string',
- };
- assertTrue(validation.validateProperty(obj, 'field', 'string'));
- assertTrue(validation.validateProperty(obj, 'field', '?string'));
-
- obj.field = 0;
- assertFalse(validation.validateProperty(obj, 'field', 'string'));
- assertFalse(validation.validateProperty(obj, 'field', '?string'));
-
- obj.field = true;
- assertFalse(validation.validateProperty(obj, 'field', 'string'));
- assertFalse(validation.validateProperty(obj, 'field', '?string'));
-
- obj.field = {};
- assertFalse(validation.validateProperty(obj, 'field', 'string'));
- assertFalse(validation.validateProperty(obj, 'field', '?string'));
-
- delete obj.field;
- assertFalse(validation.validateProperty(obj, 'field', 'string'));
- assertTrue(validation.validateProperty(obj, 'field', '?string'));
- },
-
- /** Tests validating property of type number. */
- testValidateProperty_number() {
- const obj = {
- field: 0,
- };
- assertTrue(validation.validateProperty(obj, 'field', 'number'));
- assertTrue(validation.validateProperty(obj, 'field', '?number'));
-
- obj.field = '0';
- assertFalse(validation.validateProperty(obj, 'field', 'number'));
- assertFalse(validation.validateProperty(obj, 'field', '?number'));
-
- obj.field = true;
- assertFalse(validation.validateProperty(obj, 'field', 'number'));
- assertFalse(validation.validateProperty(obj, 'field', '?number'));
-
- obj.field = {};
- assertFalse(validation.validateProperty(obj, 'field', 'number'));
- assertFalse(validation.validateProperty(obj, 'field', '?number'));
-
- delete obj.field;
- assertFalse(validation.validateProperty(obj, 'field', 'number'));
- assertTrue(validation.validateProperty(obj, 'field', '?number'));
- },
-
- /** Tests validating property of type boolean. */
- testValidateProperty_boolean() {
- const obj = {
- field: true,
- };
- assertTrue(validation.validateProperty(obj, 'field', 'boolean'));
- assertTrue(validation.validateProperty(obj, 'field', '?boolean'));
-
- obj.field = '0';
- assertFalse(validation.validateProperty(obj, 'field', 'boolean'));
- assertFalse(validation.validateProperty(obj, 'field', '?boolean'));
-
- obj.field = 1000;
- assertFalse(validation.validateProperty(obj, 'field', 'boolean'));
- assertFalse(validation.validateProperty(obj, 'field', '?boolean'));
-
- obj.field = [true];
- assertFalse(validation.validateProperty(obj, 'field', 'boolean'));
- assertFalse(validation.validateProperty(obj, 'field', '?boolean'));
-
- delete obj.field;
- assertFalse(validation.validateProperty(obj, 'field', 'boolean'));
- assertTrue(validation.validateProperty(obj, 'field', '?boolean'));
- },
-
- /** Tests validating property of type array. */
- testValidateProperty_array() {
- const obj = {
- field: [],
- };
- assertTrue(validation.validateProperty(obj, 'field', 'Array'));
- assertTrue(validation.validateProperty(obj, 'field', '?Array'));
-
- obj.field = '0';
- assertFalse(validation.validateProperty(obj, 'field', 'Array'));
- assertFalse(validation.validateProperty(obj, 'field', '?Array'));
-
- obj.field = 1000;
- assertFalse(validation.validateProperty(obj, 'field', 'Array'));
- assertFalse(validation.validateProperty(obj, 'field', '?Array'));
-
- obj.field = true;
- assertFalse(validation.validateProperty(obj, 'field', 'Array'));
- assertFalse(validation.validateProperty(obj, 'field', '?Array'));
-
- delete obj.field;
- assertFalse(validation.validateProperty(obj, 'field', 'Array'));
- assertTrue(validation.validateProperty(obj, 'field', '?Array'));
- },
-
- /** Tests validating properties of type RepeatMode */
- testValidateProperty_repeatMode() {
- const obj = {
- off: 'OFF',
- one: 'ONE',
- all: 'ALL',
- invalid: 'invalid',
- };
- assertTrue(validation.validateProperty(obj, 'off', 'RepeatMode'));
- assertTrue(validation.validateProperty(obj, 'one', 'RepeatMode'));
- assertTrue(validation.validateProperty(obj, 'all', 'RepeatMode'));
- assertFalse(validation.validateProperty(obj, 'invalid', 'RepeatMode'));
- },
-});
diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java
deleted file mode 100644
index bc38cbdb8a..0000000000
--- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DefaultReceiverPlayerManager.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.exoplayer2.castdemo;
-
-import android.content.Context;
-import android.net.Uri;
-import androidx.annotation.Nullable;
-import android.view.KeyEvent;
-import android.view.View;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
-import com.google.android.exoplayer2.ExoPlayerFactory;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Player.DiscontinuityReason;
-import com.google.android.exoplayer2.Player.EventListener;
-import com.google.android.exoplayer2.Player.TimelineChangeReason;
-import com.google.android.exoplayer2.RenderersFactory;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.Timeline.Period;
-import com.google.android.exoplayer2.ext.cast.CastPlayer;
-import com.google.android.exoplayer2.ext.cast.MediaItem;
-import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
-import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.ui.PlayerControlView;
-import com.google.android.exoplayer2.ui.PlayerView;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.gms.cast.MediaInfo;
-import com.google.android.gms.cast.MediaMetadata;
-import com.google.android.gms.cast.MediaQueueItem;
-import com.google.android.gms.cast.framework.CastContext;
-import java.util.ArrayList;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/** Manages players and an internal media queue for the ExoPlayer/Cast demo app. */
-/* package */ class DefaultReceiverPlayerManager
- implements PlayerManager, EventListener, SessionAvailabilityListener {
-
- private static final String USER_AGENT = "ExoCastDemoPlayer";
- private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY =
- new DefaultHttpDataSourceFactory(USER_AGENT);
-
- private final PlayerView localPlayerView;
- private final PlayerControlView castControlView;
- private final SimpleExoPlayer exoPlayer;
- private final CastPlayer castPlayer;
- private final ArrayList mediaQueue;
- private final Listener listener;
- private final ConcatenatingMediaSource concatenatingMediaSource;
-
- private int currentItemIndex;
- private Player currentPlayer;
-
- /**
- * Creates a new manager for {@link SimpleExoPlayer} and {@link CastPlayer}.
- *
- * @param listener A {@link Listener} for queue position changes.
- * @param localPlayerView The {@link PlayerView} for local playback.
- * @param castControlView The {@link PlayerControlView} to control remote playback.
- * @param context A {@link Context}.
- * @param castContext The {@link CastContext}.
- */
- public DefaultReceiverPlayerManager(
- Listener listener,
- PlayerView localPlayerView,
- PlayerControlView castControlView,
- Context context,
- CastContext castContext) {
- this.listener = listener;
- this.localPlayerView = localPlayerView;
- this.castControlView = castControlView;
- mediaQueue = new ArrayList<>();
- currentItemIndex = C.INDEX_UNSET;
- concatenatingMediaSource = new ConcatenatingMediaSource();
-
- DefaultTrackSelector trackSelector = new DefaultTrackSelector();
- RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
- exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector);
- exoPlayer.addListener(this);
- localPlayerView.setPlayer(exoPlayer);
-
- castPlayer = new CastPlayer(castContext);
- castPlayer.addListener(this);
- castPlayer.setSessionAvailabilityListener(this);
- castControlView.setPlayer(castPlayer);
-
- setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer);
- }
-
- // Queue manipulation methods.
-
- /**
- * Plays a specified queue item in the current player.
- *
- * @param itemIndex The index of the item to play.
- */
- @Override
- public void selectQueueItem(int itemIndex) {
- setCurrentItem(itemIndex, C.TIME_UNSET, true);
- }
-
- /** Returns the index of the currently played item. */
- @Override
- public int getCurrentItemIndex() {
- return currentItemIndex;
- }
-
- /**
- * Appends {@code item} to the media queue.
- *
- * @param item The {@link MediaItem} to append.
- */
- @Override
- public void addItem(MediaItem item) {
- mediaQueue.add(item);
- concatenatingMediaSource.addMediaSource(buildMediaSource(item));
- if (currentPlayer == castPlayer) {
- castPlayer.addItems(buildMediaQueueItem(item));
- }
- }
-
- /** Returns the size of the media queue. */
- @Override
- public int getMediaQueueSize() {
- return mediaQueue.size();
- }
-
- /**
- * Returns the item at the given index in the media queue.
- *
- * @param position The index of the item.
- * @return The item at the given index in the media queue.
- */
- @Override
- public MediaItem getItem(int position) {
- return mediaQueue.get(position);
- }
-
- /**
- * Removes the item at the given index from the media queue.
- *
- * @param item The item to remove.
- * @return Whether the removal was successful.
- */
- @Override
- public boolean removeItem(MediaItem item) {
- int itemIndex = mediaQueue.indexOf(item);
- if (itemIndex == -1) {
- return false;
- }
- concatenatingMediaSource.removeMediaSource(itemIndex);
- if (currentPlayer == castPlayer) {
- if (castPlayer.getPlaybackState() != Player.STATE_IDLE) {
- Timeline castTimeline = castPlayer.getCurrentTimeline();
- if (castTimeline.getPeriodCount() <= itemIndex) {
- return false;
- }
- castPlayer.removeItem((int) castTimeline.getPeriod(itemIndex, new Period()).id);
- }
- }
- mediaQueue.remove(itemIndex);
- if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) {
- maybeSetCurrentItemAndNotify(C.INDEX_UNSET);
- } else if (itemIndex < currentItemIndex) {
- maybeSetCurrentItemAndNotify(currentItemIndex - 1);
- }
- return true;
- }
-
- /**
- * Moves an item within the queue.
- *
- * @param item The item to move.
- * @param toIndex The target index of the item in the queue.
- * @return Whether the item move was successful.
- */
- @Override
- public boolean moveItem(MediaItem item, int toIndex) {
- int fromIndex = mediaQueue.indexOf(item);
- if (fromIndex == -1) {
- return false;
- }
- // Player update.
- concatenatingMediaSource.moveMediaSource(fromIndex, toIndex);
- if (currentPlayer == castPlayer && castPlayer.getPlaybackState() != Player.STATE_IDLE) {
- Timeline castTimeline = castPlayer.getCurrentTimeline();
- int periodCount = castTimeline.getPeriodCount();
- if (periodCount <= fromIndex || periodCount <= toIndex) {
- return false;
- }
- int elementId = (int) castTimeline.getPeriod(fromIndex, new Period()).id;
- castPlayer.moveItem(elementId, toIndex);
- }
-
- mediaQueue.add(toIndex, mediaQueue.remove(fromIndex));
-
- // Index update.
- if (fromIndex == currentItemIndex) {
- maybeSetCurrentItemAndNotify(toIndex);
- } else if (fromIndex < currentItemIndex && toIndex >= currentItemIndex) {
- maybeSetCurrentItemAndNotify(currentItemIndex - 1);
- } else if (fromIndex > currentItemIndex && toIndex <= currentItemIndex) {
- maybeSetCurrentItemAndNotify(currentItemIndex + 1);
- }
-
- return true;
- }
-
- /**
- * Dispatches a given {@link KeyEvent} to the corresponding view of the current player.
- *
- * @param event The {@link KeyEvent}.
- * @return Whether the event was handled by the target view.
- */
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (currentPlayer == exoPlayer) {
- return localPlayerView.dispatchKeyEvent(event);
- } else /* currentPlayer == castPlayer */ {
- return castControlView.dispatchKeyEvent(event);
- }
- }
-
- /** Releases the manager and the players that it holds. */
- @Override
- public void release() {
- currentItemIndex = C.INDEX_UNSET;
- mediaQueue.clear();
- concatenatingMediaSource.clear();
- castPlayer.setSessionAvailabilityListener(null);
- castPlayer.release();
- localPlayerView.setPlayer(null);
- exoPlayer.release();
- }
-
- // Player.EventListener implementation.
-
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
- updateCurrentItemIndex();
- }
-
- @Override
- public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
- updateCurrentItemIndex();
- }
-
- @Override
- public void onTimelineChanged(
- Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) {
- updateCurrentItemIndex();
- }
-
- // CastPlayer.SessionAvailabilityListener implementation.
-
- @Override
- public void onCastSessionAvailable() {
- setCurrentPlayer(castPlayer);
- }
-
- @Override
- public void onCastSessionUnavailable() {
- setCurrentPlayer(exoPlayer);
- }
-
- // Internal methods.
-
- private void updateCurrentItemIndex() {
- int playbackState = currentPlayer.getPlaybackState();
- maybeSetCurrentItemAndNotify(
- playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED
- ? currentPlayer.getCurrentWindowIndex()
- : C.INDEX_UNSET);
- }
-
- private void setCurrentPlayer(Player currentPlayer) {
- if (this.currentPlayer == currentPlayer) {
- return;
- }
-
- // View management.
- if (currentPlayer == exoPlayer) {
- localPlayerView.setVisibility(View.VISIBLE);
- castControlView.hide();
- } else /* currentPlayer == castPlayer */ {
- localPlayerView.setVisibility(View.GONE);
- castControlView.show();
- }
-
- // Player state management.
- long playbackPositionMs = C.TIME_UNSET;
- int windowIndex = C.INDEX_UNSET;
- boolean playWhenReady = false;
- if (this.currentPlayer != null) {
- int playbackState = this.currentPlayer.getPlaybackState();
- if (playbackState != Player.STATE_ENDED) {
- playbackPositionMs = this.currentPlayer.getCurrentPosition();
- playWhenReady = this.currentPlayer.getPlayWhenReady();
- windowIndex = this.currentPlayer.getCurrentWindowIndex();
- if (windowIndex != currentItemIndex) {
- playbackPositionMs = C.TIME_UNSET;
- windowIndex = currentItemIndex;
- }
- }
- this.currentPlayer.stop(true);
- } else {
- // This is the initial setup. No need to save any state.
- }
-
- this.currentPlayer = currentPlayer;
-
- // Media queue management.
- if (currentPlayer == exoPlayer) {
- exoPlayer.prepare(concatenatingMediaSource);
- }
-
- // Playback transition.
- if (windowIndex != C.INDEX_UNSET) {
- setCurrentItem(windowIndex, playbackPositionMs, playWhenReady);
- }
- }
-
- /**
- * Starts playback of the item at the given position.
- *
- * @param itemIndex The index of the item to play.
- * @param positionMs The position at which playback should start.
- * @param playWhenReady Whether the player should proceed when ready to do so.
- */
- private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) {
- maybeSetCurrentItemAndNotify(itemIndex);
- if (currentPlayer == castPlayer && castPlayer.getCurrentTimeline().isEmpty()) {
- MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()];
- for (int i = 0; i < items.length; i++) {
- items[i] = buildMediaQueueItem(mediaQueue.get(i));
- }
- castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF);
- } else {
- currentPlayer.seekTo(itemIndex, positionMs);
- currentPlayer.setPlayWhenReady(playWhenReady);
- }
- }
-
- private void maybeSetCurrentItemAndNotify(int currentItemIndex) {
- if (this.currentItemIndex != currentItemIndex) {
- int oldIndex = this.currentItemIndex;
- this.currentItemIndex = currentItemIndex;
- listener.onQueuePositionChanged(oldIndex, currentItemIndex);
- }
- }
-
- private static MediaSource buildMediaSource(MediaItem item) {
- Uri uri = item.media.uri;
- switch (item.mimeType) {
- case DemoUtil.MIME_TYPE_SS:
- return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_DASH:
- return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_HLS:
- return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_VIDEO_MP4:
- return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- default:
- {
- throw new IllegalStateException("Unsupported type: " + item.mimeType);
- }
- }
- }
-
- private static MediaQueueItem buildMediaQueueItem(MediaItem item) {
- MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
- movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title);
- MediaInfo.Builder mediaInfoBuilder =
- new MediaInfo.Builder(item.media.uri.toString())
- .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
- .setContentType(item.mimeType)
- .setMetadata(movieMetadata);
- if (!item.drmSchemes.isEmpty()) {
- MediaItem.DrmScheme scheme = item.drmSchemes.get(0);
- try {
- // This configuration is only intended for testing and should *not* be used in production
- // environments. See comment in the Cast Demo app's options provider.
- JSONObject drmConfiguration = getDrmConfigurationJson(scheme);
- if (drmConfiguration != null) {
- mediaInfoBuilder.setCustomData(drmConfiguration);
- }
- } catch (JSONException e) {
- throw new RuntimeException(e);
- }
- }
- return new MediaQueueItem.Builder(mediaInfoBuilder.build()).build();
- }
-
- @Nullable
- private static JSONObject getDrmConfigurationJson(MediaItem.DrmScheme scheme)
- throws JSONException {
- String drmScheme;
- if (C.WIDEVINE_UUID.equals(scheme.uuid)) {
- drmScheme = "widevine";
- } else if (C.PLAYREADY_UUID.equals(scheme.uuid)) {
- drmScheme = "playready";
- } else {
- return null;
- }
- MediaItem.UriBundle licenseServer = Assertions.checkNotNull(scheme.licenseServer);
- JSONObject exoplayerConfig =
- new JSONObject().put("withCredentials", false).put("protectionSystem", drmScheme);
- if (!licenseServer.uri.equals(Uri.EMPTY)) {
- exoplayerConfig.put("licenseUrl", licenseServer.uri.toString());
- }
- if (!licenseServer.requestHeaders.isEmpty()) {
- exoplayerConfig.put("headers", new JSONObject(licenseServer.requestHeaders));
- }
- return new JSONObject().put("exoPlayerConfig", exoplayerConfig);
- }
-}
diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java
index 9599da15cb..2d5a5f0ccf 100644
--- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java
+++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/DemoUtil.java
@@ -98,6 +98,11 @@ import java.util.UUID;
"https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
"Clear DASH: Tears",
MIME_TYPE_DASH));
+ samples.add(
+ new Sample(
+ "https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8",
+ "Clear HLS: Angel one",
+ MIME_TYPE_HLS));
samples.add(
new Sample(
"https://html5demos.com/assets/dizzy.mp4", "Clear MP4: Dizzy", MIME_TYPE_VIDEO_MP4));
diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/ExoCastPlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/ExoCastPlayerManager.java
deleted file mode 100644
index e8ad2c1a0d..0000000000
--- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/ExoCastPlayerManager.java
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Copyright (C) 2018 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.castdemo;
-
-import android.content.Context;
-import android.net.Uri;
-import androidx.annotation.Nullable;
-import android.view.KeyEvent;
-import android.view.View;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.DefaultRenderersFactory;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.ExoPlayerFactory;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Player.DiscontinuityReason;
-import com.google.android.exoplayer2.Player.EventListener;
-import com.google.android.exoplayer2.Player.TimelineChangeReason;
-import com.google.android.exoplayer2.RenderersFactory;
-import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.ext.cast.DefaultCastSessionManager;
-import com.google.android.exoplayer2.ext.cast.ExoCastPlayer;
-import com.google.android.exoplayer2.ext.cast.MediaItem;
-import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
-import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.source.dash.DashMediaSource;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
-import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.ui.PlayerControlView;
-import com.google.android.exoplayer2.ui.PlayerView;
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Log;
-import com.google.android.gms.cast.framework.CastContext;
-import java.util.ArrayList;
-
-/** Manages players and an internal media queue for the Cast demo app. */
-/* package */ class ExoCastPlayerManager
- implements PlayerManager, EventListener, SessionAvailabilityListener {
-
- private static final String TAG = "ExoCastPlayerManager";
- private static final String USER_AGENT = "ExoCastDemoPlayer";
- private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY =
- new DefaultHttpDataSourceFactory(USER_AGENT);
-
- private final PlayerView localPlayerView;
- private final PlayerControlView castControlView;
- private final SimpleExoPlayer exoPlayer;
- private final ExoCastPlayer exoCastPlayer;
- private final ArrayList mediaQueue;
- private final Listener listener;
- private final ConcatenatingMediaSource concatenatingMediaSource;
-
- private int currentItemIndex;
- private Player currentPlayer;
-
- /**
- * Creates a new manager for {@link SimpleExoPlayer} and {@link ExoCastPlayer}.
- *
- * @param listener A {@link Listener}.
- * @param localPlayerView The {@link PlayerView} for local playback.
- * @param castControlView The {@link PlayerControlView} to control remote playback.
- * @param context A {@link Context}.
- * @param castContext The {@link CastContext}.
- */
- public ExoCastPlayerManager(
- Listener listener,
- PlayerView localPlayerView,
- PlayerControlView castControlView,
- Context context,
- CastContext castContext) {
- this.listener = listener;
- this.localPlayerView = localPlayerView;
- this.castControlView = castControlView;
- mediaQueue = new ArrayList<>();
- currentItemIndex = C.INDEX_UNSET;
- concatenatingMediaSource = new ConcatenatingMediaSource();
-
- DefaultTrackSelector trackSelector = new DefaultTrackSelector();
- RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
- exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector);
- exoPlayer.addListener(this);
- localPlayerView.setPlayer(exoPlayer);
-
- exoCastPlayer =
- new ExoCastPlayer(
- sessionManagerListener ->
- new DefaultCastSessionManager(castContext, sessionManagerListener));
- exoCastPlayer.addListener(this);
- exoCastPlayer.setSessionAvailabilityListener(this);
- castControlView.setPlayer(exoCastPlayer);
-
- setCurrentPlayer(exoCastPlayer.isCastSessionAvailable() ? exoCastPlayer : exoPlayer);
- }
-
- // Queue manipulation methods.
-
- /**
- * Plays a specified queue item in the current player.
- *
- * @param itemIndex The index of the item to play.
- */
- @Override
- public void selectQueueItem(int itemIndex) {
- setCurrentItem(itemIndex, C.TIME_UNSET, true);
- }
-
- /** Returns the index of the currently played item. */
- @Override
- public int getCurrentItemIndex() {
- return currentItemIndex;
- }
-
- /**
- * Appends {@code item} to the media queue.
- *
- * @param item The {@link MediaItem} to append.
- */
- @Override
- public void addItem(MediaItem item) {
- mediaQueue.add(item);
- concatenatingMediaSource.addMediaSource(buildMediaSource(item));
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.addItemsToQueue(item);
- }
- }
-
- /** Returns the size of the media queue. */
- @Override
- public int getMediaQueueSize() {
- return mediaQueue.size();
- }
-
- /**
- * Returns the item at the given index in the media queue.
- *
- * @param position The index of the item.
- * @return The item at the given index in the media queue.
- */
- @Override
- public MediaItem getItem(int position) {
- return mediaQueue.get(position);
- }
-
- /**
- * Removes the item at the given index from the media queue.
- *
- * @param item The item to remove.
- * @return Whether the removal was successful.
- */
- @Override
- public boolean removeItem(MediaItem item) {
- int itemIndex = mediaQueue.indexOf(item);
- if (itemIndex == -1) {
- // This may happen if another sender app removes items while this sender app is in "swiping
- // an item" state.
- return false;
- }
- concatenatingMediaSource.removeMediaSource(itemIndex);
- mediaQueue.remove(itemIndex);
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.removeItemFromQueue(itemIndex);
- }
- if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) {
- maybeSetCurrentItemAndNotify(C.INDEX_UNSET);
- } else if (itemIndex < currentItemIndex) {
- maybeSetCurrentItemAndNotify(currentItemIndex - 1);
- }
- return true;
- }
-
- /**
- * Moves an item within the queue.
- *
- * @param item The item to move. This method does nothing if {@code item} is not contained in the
- * queue.
- * @param toIndex The target index of the item in the queue. If {@code toIndex} exceeds the last
- * position in the queue, {@code toIndex} is clamped to match the largest possible value.
- * @return True if {@code item} was contained in the queue, and {@code toIndex} was a valid
- * position. False otherwise.
- */
- @Override
- public boolean moveItem(MediaItem item, int toIndex) {
- int indexOfItem = mediaQueue.indexOf(item);
- if (indexOfItem == -1) {
- // This may happen if another sender app removes items while this sender app is in "dragging
- // an item" state.
- return false;
- }
- int clampedToIndex = Math.min(toIndex, mediaQueue.size() - 1);
- mediaQueue.add(clampedToIndex, mediaQueue.remove(indexOfItem));
- concatenatingMediaSource.moveMediaSource(indexOfItem, clampedToIndex);
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.moveItemInQueue(indexOfItem, clampedToIndex);
- }
- // Index update.
- maybeSetCurrentItemAndNotify(currentPlayer.getCurrentWindowIndex());
- return clampedToIndex == toIndex;
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (currentPlayer == exoPlayer) {
- return localPlayerView.dispatchKeyEvent(event);
- } else /* currentPlayer == exoCastPlayer */ {
- return castControlView.dispatchKeyEvent(event);
- }
- }
-
- /** Releases the manager and the players that it holds. */
- @Override
- public void release() {
- currentItemIndex = C.INDEX_UNSET;
- mediaQueue.clear();
- concatenatingMediaSource.clear();
- exoCastPlayer.setSessionAvailabilityListener(null);
- exoCastPlayer.release();
- localPlayerView.setPlayer(null);
- exoPlayer.release();
- }
-
- // Player.EventListener implementation.
-
- @Override
- public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
- updateCurrentItemIndex();
- }
-
- @Override
- public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
- updateCurrentItemIndex();
- }
-
- @Override
- public void onTimelineChanged(
- Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) {
- if (currentPlayer == exoCastPlayer && reason != Player.TIMELINE_CHANGE_REASON_RESET) {
- maybeUpdateLocalQueueWithRemoteQueueAndNotify();
- }
- updateCurrentItemIndex();
- }
-
- @Override
- public void onPlayerError(ExoPlaybackException error) {
- Log.e(TAG, "The player encountered an error.", error);
- listener.onPlayerError();
- }
-
- // CastPlayer.SessionAvailabilityListener implementation.
-
- @Override
- public void onCastSessionAvailable() {
- setCurrentPlayer(exoCastPlayer);
- }
-
- @Override
- public void onCastSessionUnavailable() {
- setCurrentPlayer(exoPlayer);
- }
-
- // Internal methods.
-
- private void maybeUpdateLocalQueueWithRemoteQueueAndNotify() {
- Assertions.checkState(currentPlayer == exoCastPlayer);
- boolean mediaQueuesMatch = mediaQueue.size() == exoCastPlayer.getQueueSize();
- for (int i = 0; mediaQueuesMatch && i < mediaQueue.size(); i++) {
- mediaQueuesMatch = mediaQueue.get(i).uuid.equals(exoCastPlayer.getQueueItem(i).uuid);
- }
- if (mediaQueuesMatch) {
- // The media queues match. Do nothing.
- return;
- }
- mediaQueue.clear();
- concatenatingMediaSource.clear();
- for (int i = 0; i < exoCastPlayer.getQueueSize(); i++) {
- MediaItem item = exoCastPlayer.getQueueItem(i);
- mediaQueue.add(item);
- concatenatingMediaSource.addMediaSource(buildMediaSource(item));
- }
- listener.onQueueContentsExternallyChanged();
- }
-
- private void updateCurrentItemIndex() {
- int playbackState = currentPlayer.getPlaybackState();
- maybeSetCurrentItemAndNotify(
- playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED
- ? currentPlayer.getCurrentWindowIndex()
- : C.INDEX_UNSET);
- }
-
- private void setCurrentPlayer(Player currentPlayer) {
- if (this.currentPlayer == currentPlayer) {
- return;
- }
-
- // View management.
- if (currentPlayer == exoPlayer) {
- localPlayerView.setVisibility(View.VISIBLE);
- castControlView.hide();
- } else /* currentPlayer == exoCastPlayer */ {
- localPlayerView.setVisibility(View.GONE);
- castControlView.show();
- }
-
- // Player state management.
- long playbackPositionMs = C.TIME_UNSET;
- int windowIndex = C.INDEX_UNSET;
- boolean playWhenReady = false;
- if (this.currentPlayer != null) {
- int playbackState = this.currentPlayer.getPlaybackState();
- if (playbackState != Player.STATE_ENDED) {
- playbackPositionMs = this.currentPlayer.getCurrentPosition();
- playWhenReady = this.currentPlayer.getPlayWhenReady();
- windowIndex = this.currentPlayer.getCurrentWindowIndex();
- if (windowIndex != currentItemIndex) {
- playbackPositionMs = C.TIME_UNSET;
- windowIndex = currentItemIndex;
- }
- }
- this.currentPlayer.stop(true);
- } else {
- // This is the initial setup. No need to save any state.
- }
-
- this.currentPlayer = currentPlayer;
-
- // Media queue management.
- boolean shouldSeekInNewCurrentPlayer;
- if (currentPlayer == exoPlayer) {
- exoPlayer.prepare(concatenatingMediaSource);
- shouldSeekInNewCurrentPlayer = true;
- } else /* currentPlayer == exoCastPlayer */ {
- if (exoCastPlayer.getPlaybackState() == Player.STATE_IDLE) {
- exoCastPlayer.prepare();
- }
- if (mediaQueue.isEmpty()) {
- // Casting started with no local queue. We take the receiver app's queue as our own.
- maybeUpdateLocalQueueWithRemoteQueueAndNotify();
- shouldSeekInNewCurrentPlayer = false;
- } else {
- // Casting started when the sender app had no queue. We just load our items into the
- // receiver app's queue. If the receiver had no items in its queue, we also seek to wherever
- // the sender app was playing.
- int currentExoCastPlayerState = exoCastPlayer.getPlaybackState();
- shouldSeekInNewCurrentPlayer =
- currentExoCastPlayerState == Player.STATE_IDLE
- || currentExoCastPlayerState == Player.STATE_ENDED;
- exoCastPlayer.addItemsToQueue(mediaQueue.toArray(new MediaItem[0]));
- }
- }
-
- // Playback transition.
- if (shouldSeekInNewCurrentPlayer && windowIndex != C.INDEX_UNSET) {
- setCurrentItem(windowIndex, playbackPositionMs, playWhenReady);
- } else if (getMediaQueueSize() > 0) {
- maybeSetCurrentItemAndNotify(currentPlayer.getCurrentWindowIndex());
- }
- }
-
- /**
- * Starts playback of the item at the given position.
- *
- * @param itemIndex The index of the item to play.
- * @param positionMs The position at which playback should start.
- * @param playWhenReady Whether the player should proceed when ready to do so.
- */
- private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) {
- maybeSetCurrentItemAndNotify(itemIndex);
- currentPlayer.seekTo(itemIndex, positionMs);
- if (currentPlayer.getPlaybackState() == Player.STATE_IDLE) {
- if (currentPlayer == exoCastPlayer) {
- exoCastPlayer.prepare();
- } else {
- exoPlayer.prepare(concatenatingMediaSource);
- }
- }
- currentPlayer.setPlayWhenReady(playWhenReady);
- }
-
- private void maybeSetCurrentItemAndNotify(int currentItemIndex) {
- if (this.currentItemIndex != currentItemIndex) {
- int oldIndex = this.currentItemIndex;
- this.currentItemIndex = currentItemIndex;
- listener.onQueuePositionChanged(oldIndex, currentItemIndex);
- }
- }
-
- private static MediaSource buildMediaSource(MediaItem item) {
- Uri uri = item.media.uri;
- switch (item.mimeType) {
- case DemoUtil.MIME_TYPE_SS:
- return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_DASH:
- return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_HLS:
- return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- case DemoUtil.MIME_TYPE_VIDEO_MP4:
- return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
- default:
- {
- throw new IllegalStateException("Unsupported type: " + item.mimeType);
- }
- }
- }
-}
diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
index 5ed434eed6..c17c0a62ab 100644
--- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
+++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/MainActivity.java
@@ -34,14 +34,11 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
-import android.widget.Toast;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.SimpleExoPlayer;
-import com.google.android.exoplayer2.ext.cast.DefaultCastOptionsProvider;
import com.google.android.exoplayer2.ext.cast.MediaItem;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.PlayerView;
-import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.dynamite.DynamiteModule;
@@ -120,21 +117,13 @@ public class MainActivity extends AppCompatActivity
// There is no Cast context to work with. Do nothing.
return;
}
- String applicationId = castContext.getCastOptions().getReceiverApplicationId();
- switch (applicationId) {
- case CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID:
- case DefaultCastOptionsProvider.APP_ID_DEFAULT_RECEIVER_WITH_DRM:
- playerManager =
- new DefaultReceiverPlayerManager(
- /* listener= */ this,
- localPlayerView,
- castControlView,
- /* context= */ this,
- castContext);
- break;
- default:
- throw new IllegalStateException("Illegal receiver app id: " + applicationId);
- }
+ playerManager =
+ new PlayerManager(
+ /* listener= */ this,
+ localPlayerView,
+ castControlView,
+ /* context= */ this,
+ castContext);
mediaQueueList.setAdapter(mediaQueueListAdapter);
}
@@ -181,16 +170,6 @@ public class MainActivity extends AppCompatActivity
}
}
- @Override
- public void onQueueContentsExternallyChanged() {
- mediaQueueListAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void onPlayerError() {
- Toast.makeText(getApplicationContext(), R.string.player_error_msg, Toast.LENGTH_LONG).show();
- }
-
// Internal methods.
private View buildSampleListView() {
diff --git a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
index c9a728b3ff..c92ebd7e94 100644
--- a/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
+++ b/demos/cast/src/main/java/com/google/android/exoplayer2/castdemo/PlayerManager.java
@@ -15,53 +15,419 @@
*/
package com.google.android.exoplayer2.castdemo;
+import android.content.Context;
+import android.net.Uri;
+import androidx.annotation.Nullable;
import android.view.KeyEvent;
+import android.view.View;
import com.google.android.exoplayer2.C;
+import com.google.android.exoplayer2.DefaultRenderersFactory;
+import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.Player.DiscontinuityReason;
+import com.google.android.exoplayer2.Player.EventListener;
+import com.google.android.exoplayer2.Player.TimelineChangeReason;
+import com.google.android.exoplayer2.RenderersFactory;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.Timeline;
+import com.google.android.exoplayer2.Timeline.Period;
+import com.google.android.exoplayer2.ext.cast.CastPlayer;
import com.google.android.exoplayer2.ext.cast.MediaItem;
+import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener;
+import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
+import com.google.android.exoplayer2.source.MediaSource;
+import com.google.android.exoplayer2.source.ProgressiveMediaSource;
+import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.source.hls.HlsMediaSource;
+import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import com.google.android.exoplayer2.ui.PlayerControlView;
+import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
+import com.google.android.exoplayer2.util.Assertions;
+import com.google.android.gms.cast.MediaInfo;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.MediaQueueItem;
+import com.google.android.gms.cast.framework.CastContext;
+import java.util.ArrayList;
+import org.json.JSONException;
+import org.json.JSONObject;
-/** Manages the players in the Cast demo app. */
-/* package */ interface PlayerManager {
+/** Manages players and an internal media queue for the demo app. */
+/* package */ class PlayerManager implements EventListener, SessionAvailabilityListener {
/** Listener for events. */
interface Listener {
/** Called when the currently played item of the media queue changes. */
void onQueuePositionChanged(int previousIndex, int newIndex);
-
- /** Called when the media queue changes due to modifications not caused by this manager. */
- void onQueueContentsExternallyChanged();
-
- /** Called when an error occurs in the current player. */
- void onPlayerError();
}
- /** Redirects the given {@code keyEvent} to the active player. */
- boolean dispatchKeyEvent(KeyEvent keyEvent);
+ private static final String USER_AGENT = "ExoCastDemoPlayer";
+ private static final DefaultHttpDataSourceFactory DATA_SOURCE_FACTORY =
+ new DefaultHttpDataSourceFactory(USER_AGENT);
- /** Appends the given {@link MediaItem} to the media queue. */
- void addItem(MediaItem mediaItem);
+ private final PlayerView localPlayerView;
+ private final PlayerControlView castControlView;
+ private final SimpleExoPlayer exoPlayer;
+ private final CastPlayer castPlayer;
+ private final ArrayList mediaQueue;
+ private final Listener listener;
+ private final ConcatenatingMediaSource concatenatingMediaSource;
- /** Returns the number of items in the media queue. */
- int getMediaQueueSize();
-
- /** Selects the item at the given position for playback. */
- void selectQueueItem(int position);
+ private int currentItemIndex;
+ private Player currentPlayer;
/**
- * Returns the position of the item currently being played, or {@link C#INDEX_UNSET} if no item is
- * being played.
+ * Creates a new manager for {@link SimpleExoPlayer} and {@link CastPlayer}.
+ *
+ * @param listener A {@link Listener} for queue position changes.
+ * @param localPlayerView The {@link PlayerView} for local playback.
+ * @param castControlView The {@link PlayerControlView} to control remote playback.
+ * @param context A {@link Context}.
+ * @param castContext The {@link CastContext}.
*/
- int getCurrentItemIndex();
+ public PlayerManager(
+ Listener listener,
+ PlayerView localPlayerView,
+ PlayerControlView castControlView,
+ Context context,
+ CastContext castContext) {
+ this.listener = listener;
+ this.localPlayerView = localPlayerView;
+ this.castControlView = castControlView;
+ mediaQueue = new ArrayList<>();
+ currentItemIndex = C.INDEX_UNSET;
+ concatenatingMediaSource = new ConcatenatingMediaSource();
- /** Returns the {@link MediaItem} at the given {@code position}. */
- MediaItem getItem(int position);
+ DefaultTrackSelector trackSelector = new DefaultTrackSelector();
+ RenderersFactory renderersFactory = new DefaultRenderersFactory(context);
+ exoPlayer = ExoPlayerFactory.newSimpleInstance(context, renderersFactory, trackSelector);
+ exoPlayer.addListener(this);
+ localPlayerView.setPlayer(exoPlayer);
- /** Moves the item at position {@code from} to position {@code to}. */
- boolean moveItem(MediaItem item, int to);
+ castPlayer = new CastPlayer(castContext);
+ castPlayer.addListener(this);
+ castPlayer.setSessionAvailabilityListener(this);
+ castControlView.setPlayer(castPlayer);
- /** Removes the item at position {@code index}. */
- boolean removeItem(MediaItem item);
+ setCurrentPlayer(castPlayer.isCastSessionAvailable() ? castPlayer : exoPlayer);
+ }
- /** Releases any acquired resources. */
- void release();
+ // Queue manipulation methods.
+
+ /**
+ * Plays a specified queue item in the current player.
+ *
+ * @param itemIndex The index of the item to play.
+ */
+ public void selectQueueItem(int itemIndex) {
+ setCurrentItem(itemIndex, C.TIME_UNSET, true);
+ }
+
+ /** Returns the index of the currently played item. */
+ public int getCurrentItemIndex() {
+ return currentItemIndex;
+ }
+
+ /**
+ * Appends {@code item} to the media queue.
+ *
+ * @param item The {@link MediaItem} to append.
+ */
+ public void addItem(MediaItem item) {
+ mediaQueue.add(item);
+ concatenatingMediaSource.addMediaSource(buildMediaSource(item));
+ if (currentPlayer == castPlayer) {
+ castPlayer.addItems(buildMediaQueueItem(item));
+ }
+ }
+
+ /** Returns the size of the media queue. */
+ public int getMediaQueueSize() {
+ return mediaQueue.size();
+ }
+
+ /**
+ * Returns the item at the given index in the media queue.
+ *
+ * @param position The index of the item.
+ * @return The item at the given index in the media queue.
+ */
+ public MediaItem getItem(int position) {
+ return mediaQueue.get(position);
+ }
+
+ /**
+ * Removes the item at the given index from the media queue.
+ *
+ * @param item The item to remove.
+ * @return Whether the removal was successful.
+ */
+ public boolean removeItem(MediaItem item) {
+ int itemIndex = mediaQueue.indexOf(item);
+ if (itemIndex == -1) {
+ return false;
+ }
+ concatenatingMediaSource.removeMediaSource(itemIndex);
+ if (currentPlayer == castPlayer) {
+ if (castPlayer.getPlaybackState() != Player.STATE_IDLE) {
+ Timeline castTimeline = castPlayer.getCurrentTimeline();
+ if (castTimeline.getPeriodCount() <= itemIndex) {
+ return false;
+ }
+ castPlayer.removeItem((int) castTimeline.getPeriod(itemIndex, new Period()).id);
+ }
+ }
+ mediaQueue.remove(itemIndex);
+ if (itemIndex == currentItemIndex && itemIndex == mediaQueue.size()) {
+ maybeSetCurrentItemAndNotify(C.INDEX_UNSET);
+ } else if (itemIndex < currentItemIndex) {
+ maybeSetCurrentItemAndNotify(currentItemIndex - 1);
+ }
+ return true;
+ }
+
+ /**
+ * Moves an item within the queue.
+ *
+ * @param item The item to move.
+ * @param toIndex The target index of the item in the queue.
+ * @return Whether the item move was successful.
+ */
+ public boolean moveItem(MediaItem item, int toIndex) {
+ int fromIndex = mediaQueue.indexOf(item);
+ if (fromIndex == -1) {
+ return false;
+ }
+ // Player update.
+ concatenatingMediaSource.moveMediaSource(fromIndex, toIndex);
+ if (currentPlayer == castPlayer && castPlayer.getPlaybackState() != Player.STATE_IDLE) {
+ Timeline castTimeline = castPlayer.getCurrentTimeline();
+ int periodCount = castTimeline.getPeriodCount();
+ if (periodCount <= fromIndex || periodCount <= toIndex) {
+ return false;
+ }
+ int elementId = (int) castTimeline.getPeriod(fromIndex, new Period()).id;
+ castPlayer.moveItem(elementId, toIndex);
+ }
+
+ mediaQueue.add(toIndex, mediaQueue.remove(fromIndex));
+
+ // Index update.
+ if (fromIndex == currentItemIndex) {
+ maybeSetCurrentItemAndNotify(toIndex);
+ } else if (fromIndex < currentItemIndex && toIndex >= currentItemIndex) {
+ maybeSetCurrentItemAndNotify(currentItemIndex - 1);
+ } else if (fromIndex > currentItemIndex && toIndex <= currentItemIndex) {
+ maybeSetCurrentItemAndNotify(currentItemIndex + 1);
+ }
+
+ return true;
+ }
+
+ /**
+ * Dispatches a given {@link KeyEvent} to the corresponding view of the current player.
+ *
+ * @param event The {@link KeyEvent}.
+ * @return Whether the event was handled by the target view.
+ */
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (currentPlayer == exoPlayer) {
+ return localPlayerView.dispatchKeyEvent(event);
+ } else /* currentPlayer == castPlayer */ {
+ return castControlView.dispatchKeyEvent(event);
+ }
+ }
+
+ /** Releases the manager and the players that it holds. */
+ public void release() {
+ currentItemIndex = C.INDEX_UNSET;
+ mediaQueue.clear();
+ concatenatingMediaSource.clear();
+ castPlayer.setSessionAvailabilityListener(null);
+ castPlayer.release();
+ localPlayerView.setPlayer(null);
+ exoPlayer.release();
+ }
+
+ // Player.EventListener implementation.
+
+ @Override
+ public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
+ updateCurrentItemIndex();
+ }
+
+ @Override
+ public void onPositionDiscontinuity(@DiscontinuityReason int reason) {
+ updateCurrentItemIndex();
+ }
+
+ @Override
+ public void onTimelineChanged(
+ Timeline timeline, @Nullable Object manifest, @TimelineChangeReason int reason) {
+ updateCurrentItemIndex();
+ }
+
+ // CastPlayer.SessionAvailabilityListener implementation.
+
+ @Override
+ public void onCastSessionAvailable() {
+ setCurrentPlayer(castPlayer);
+ }
+
+ @Override
+ public void onCastSessionUnavailable() {
+ setCurrentPlayer(exoPlayer);
+ }
+
+ // Internal methods.
+
+ private void updateCurrentItemIndex() {
+ int playbackState = currentPlayer.getPlaybackState();
+ maybeSetCurrentItemAndNotify(
+ playbackState != Player.STATE_IDLE && playbackState != Player.STATE_ENDED
+ ? currentPlayer.getCurrentWindowIndex()
+ : C.INDEX_UNSET);
+ }
+
+ private void setCurrentPlayer(Player currentPlayer) {
+ if (this.currentPlayer == currentPlayer) {
+ return;
+ }
+
+ // View management.
+ if (currentPlayer == exoPlayer) {
+ localPlayerView.setVisibility(View.VISIBLE);
+ castControlView.hide();
+ } else /* currentPlayer == castPlayer */ {
+ localPlayerView.setVisibility(View.GONE);
+ castControlView.show();
+ }
+
+ // Player state management.
+ long playbackPositionMs = C.TIME_UNSET;
+ int windowIndex = C.INDEX_UNSET;
+ boolean playWhenReady = false;
+
+ Player previousPlayer = this.currentPlayer;
+ if (previousPlayer != null) {
+ // Save state from the previous player.
+ int playbackState = previousPlayer.getPlaybackState();
+ if (playbackState != Player.STATE_ENDED) {
+ playbackPositionMs = previousPlayer.getCurrentPosition();
+ playWhenReady = previousPlayer.getPlayWhenReady();
+ windowIndex = previousPlayer.getCurrentWindowIndex();
+ if (windowIndex != currentItemIndex) {
+ playbackPositionMs = C.TIME_UNSET;
+ windowIndex = currentItemIndex;
+ }
+ }
+ previousPlayer.stop(true);
+ }
+
+ this.currentPlayer = currentPlayer;
+
+ // Media queue management.
+ if (currentPlayer == exoPlayer) {
+ exoPlayer.prepare(concatenatingMediaSource);
+ }
+
+ // Playback transition.
+ if (windowIndex != C.INDEX_UNSET) {
+ setCurrentItem(windowIndex, playbackPositionMs, playWhenReady);
+ }
+ }
+
+ /**
+ * Starts playback of the item at the given position.
+ *
+ * @param itemIndex The index of the item to play.
+ * @param positionMs The position at which playback should start.
+ * @param playWhenReady Whether the player should proceed when ready to do so.
+ */
+ private void setCurrentItem(int itemIndex, long positionMs, boolean playWhenReady) {
+ maybeSetCurrentItemAndNotify(itemIndex);
+ if (currentPlayer == castPlayer && castPlayer.getCurrentTimeline().isEmpty()) {
+ MediaQueueItem[] items = new MediaQueueItem[mediaQueue.size()];
+ for (int i = 0; i < items.length; i++) {
+ items[i] = buildMediaQueueItem(mediaQueue.get(i));
+ }
+ castPlayer.loadItems(items, itemIndex, positionMs, Player.REPEAT_MODE_OFF);
+ } else {
+ currentPlayer.seekTo(itemIndex, positionMs);
+ currentPlayer.setPlayWhenReady(playWhenReady);
+ }
+ }
+
+ private void maybeSetCurrentItemAndNotify(int currentItemIndex) {
+ if (this.currentItemIndex != currentItemIndex) {
+ int oldIndex = this.currentItemIndex;
+ this.currentItemIndex = currentItemIndex;
+ listener.onQueuePositionChanged(oldIndex, currentItemIndex);
+ }
+ }
+
+ private static MediaSource buildMediaSource(MediaItem item) {
+ Uri uri = item.media.uri;
+ switch (item.mimeType) {
+ case DemoUtil.MIME_TYPE_SS:
+ return new SsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ case DemoUtil.MIME_TYPE_DASH:
+ return new DashMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ case DemoUtil.MIME_TYPE_HLS:
+ return new HlsMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ case DemoUtil.MIME_TYPE_VIDEO_MP4:
+ return new ProgressiveMediaSource.Factory(DATA_SOURCE_FACTORY).createMediaSource(uri);
+ default:
+ throw new IllegalStateException("Unsupported type: " + item.mimeType);
+ }
+ }
+
+ private static MediaQueueItem buildMediaQueueItem(MediaItem item) {
+ MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);
+ movieMetadata.putString(MediaMetadata.KEY_TITLE, item.title);
+ MediaInfo.Builder mediaInfoBuilder =
+ new MediaInfo.Builder(item.media.uri.toString())
+ .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
+ .setContentType(item.mimeType)
+ .setMetadata(movieMetadata);
+ if (!item.drmSchemes.isEmpty()) {
+ MediaItem.DrmScheme scheme = item.drmSchemes.get(0);
+ try {
+ // This configuration is only intended for testing and should *not* be used in production
+ // environments. See comment in the Cast Demo app's options provider.
+ JSONObject drmConfiguration = getDrmConfigurationJson(scheme);
+ if (drmConfiguration != null) {
+ mediaInfoBuilder.setCustomData(drmConfiguration);
+ }
+ } catch (JSONException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return new MediaQueueItem.Builder(mediaInfoBuilder.build()).build();
+ }
+
+ @Nullable
+ private static JSONObject getDrmConfigurationJson(MediaItem.DrmScheme scheme)
+ throws JSONException {
+ String drmScheme;
+ if (C.WIDEVINE_UUID.equals(scheme.uuid)) {
+ drmScheme = "widevine";
+ } else if (C.PLAYREADY_UUID.equals(scheme.uuid)) {
+ drmScheme = "playready";
+ } else {
+ return null;
+ }
+ MediaItem.UriBundle licenseServer = Assertions.checkNotNull(scheme.licenseServer);
+ JSONObject exoplayerConfig =
+ new JSONObject().put("withCredentials", false).put("protectionSystem", drmScheme);
+ if (!licenseServer.uri.equals(Uri.EMPTY)) {
+ exoplayerConfig.put("licenseUrl", licenseServer.uri.toString());
+ }
+ if (!licenseServer.requestHeaders.isEmpty()) {
+ exoplayerConfig.put("headers", new JSONObject(licenseServer.requestHeaders));
+ }
+ return new JSONObject().put("exoPlayerConfig", exoplayerConfig);
+ }
}
diff --git a/demos/cast/src/main/res/values/strings.xml b/demos/cast/src/main/res/values/strings.xml
index 013b50a175..2f0acd4808 100644
--- a/demos/cast/src/main/res/values/strings.xml
+++ b/demos/cast/src/main/res/values/strings.xml
@@ -24,6 +24,4 @@
Failed to get Cast context. Try updating Google Play Services and restart the app.
- Player error encountered. Select a queue item to reprepare. Check the logcat and receiver app\'s console for more info.
-
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastSessionManager.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastSessionManager.java
deleted file mode 100644
index 7c1f06e8d2..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/CastSessionManager.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-/** Handles communication with the receiver app using a cast session. */
-public interface CastSessionManager {
-
- /** Factory for {@link CastSessionManager} instances. */
- interface Factory {
-
- /**
- * Creates a {@link CastSessionManager} instance with the given listener.
- *
- * @param listener The listener to notify on receiver app and session state updates.
- * @return The created instance.
- */
- CastSessionManager create(StateListener listener);
- }
-
- /**
- * Extends {@link SessionAvailabilityListener} by adding receiver app state notifications.
- *
- * Receiver app state notifications contain a sequence number that matches the sequence number
- * of the last {@link ExoCastMessage} sent (using {@link #send(ExoCastMessage)}) by this session
- * manager and processed by the receiver app. Sequence numbers are non-negative numbers.
- */
- interface StateListener extends SessionAvailabilityListener {
-
- /**
- * Called when a status update is received from the Cast Receiver app.
- *
- * @param stateUpdate A {@link ReceiverAppStateUpdate} containing the fields included in the
- * message.
- */
- void onStateUpdateFromReceiverApp(ReceiverAppStateUpdate stateUpdate);
- }
-
- /**
- * Special constant representing an unset sequence number. It is guaranteed to be a negative
- * value.
- */
- long SEQUENCE_NUMBER_UNSET = Long.MIN_VALUE;
-
- /**
- * Connects the session manager to the cast message bus and starts listening for session
- * availability changes. Also announces that this sender app is connected to the message bus.
- */
- void start();
-
- /** Stops tracking the state of the cast session and closes any existing session. */
- void stopTrackingSession();
-
- /**
- * Same as {@link #stopTrackingSession()}, but also stops the receiver app if a session is
- * currently available.
- */
- void stopTrackingSessionAndCasting();
-
- /** Whether a cast session is available. */
- boolean isCastSessionAvailable();
-
- /**
- * Sends an {@link ExoCastMessage} to the receiver app.
- *
- *
A sequence number is assigned to every sent message. Message senders may mask the local
- * state until a status update from the receiver app (see {@link StateListener}) is received with
- * a greater or equal sequence number.
- *
- * @param message The message to send.
- * @return The sequence number assigned to the message.
- */
- long send(ExoCastMessage message);
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java
index 5aed1373e5..8948173f60 100644
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java
+++ b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastOptionsProvider.java
@@ -44,7 +44,7 @@ public final class DefaultCastOptionsProvider implements OptionsProvider {
* do not require DRM, the default receiver app should be used (see {@link
* #APP_ID_DEFAULT_RECEIVER}).
*/
- // TODO: Add a documentation resource link for DRM support in the receiver app [Internal ref:
+ // TODO: Add a documentation resource link for DRM support in the receiver app [Internal ref:
// b/128603245].
public static final String APP_ID_DEFAULT_RECEIVER_WITH_DRM = "A12D4273";
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastSessionManager.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastSessionManager.java
deleted file mode 100644
index c08a9bc352..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/DefaultCastSessionManager.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Log;
-import com.google.android.gms.cast.Cast;
-import com.google.android.gms.cast.CastDevice;
-import com.google.android.gms.cast.framework.CastContext;
-import com.google.android.gms.cast.framework.CastSession;
-import com.google.android.gms.cast.framework.SessionManager;
-import com.google.android.gms.cast.framework.SessionManagerListener;
-import java.io.IOException;
-import org.json.JSONException;
-
-/** Implements {@link CastSessionManager} by using JSON message passing. */
-public class DefaultCastSessionManager implements CastSessionManager {
-
- private static final String TAG = "DefaultCastSessionManager";
- private static final String EXOPLAYER_CAST_NAMESPACE = "urn:x-cast:com.google.exoplayer.cast";
-
- private final SessionManager sessionManager;
- private final CastSessionListener castSessionListener;
- private final StateListener stateListener;
- private final Cast.MessageReceivedCallback messageReceivedCallback;
-
- private boolean started;
- private long sequenceNumber;
- private long expectedInitialStateUpdateSequence;
- @Nullable private CastSession currentSession;
-
- /**
- * @param context The Cast context from which the cast session is obtained.
- * @param stateListener The listener to notify of state changes.
- */
- public DefaultCastSessionManager(CastContext context, StateListener stateListener) {
- this.stateListener = stateListener;
- sessionManager = context.getSessionManager();
- currentSession = sessionManager.getCurrentCastSession();
- castSessionListener = new CastSessionListener();
- messageReceivedCallback = new CastMessageCallback();
- expectedInitialStateUpdateSequence = SEQUENCE_NUMBER_UNSET;
- }
-
- @Override
- public void start() {
- started = true;
- sessionManager.addSessionManagerListener(castSessionListener, CastSession.class);
- currentSession = sessionManager.getCurrentCastSession();
- if (currentSession != null) {
- setMessageCallbackOnSession();
- }
- }
-
- @Override
- public void stopTrackingSession() {
- stop(/* stopCasting= */ false);
- }
-
- @Override
- public void stopTrackingSessionAndCasting() {
- stop(/* stopCasting= */ true);
- }
-
- @Override
- public boolean isCastSessionAvailable() {
- return currentSession != null && expectedInitialStateUpdateSequence == SEQUENCE_NUMBER_UNSET;
- }
-
- @Override
- public long send(ExoCastMessage message) {
- if (currentSession != null) {
- currentSession.sendMessage(EXOPLAYER_CAST_NAMESPACE, message.toJsonString(sequenceNumber));
- } else {
- Log.w(TAG, "Tried to send a message with no established session. Method: " + message.method);
- }
- return sequenceNumber++;
- }
-
- private void stop(boolean stopCasting) {
- sessionManager.removeSessionManagerListener(castSessionListener, CastSession.class);
- if (currentSession != null) {
- sessionManager.endCurrentSession(stopCasting);
- }
- currentSession = null;
- started = false;
- }
-
- private void setCastSession(@Nullable CastSession session) {
- Assertions.checkState(started);
- boolean hadSession = currentSession != null;
- currentSession = session;
- if (!hadSession && session != null) {
- setMessageCallbackOnSession();
- } else if (hadSession && session == null) {
- stateListener.onCastSessionUnavailable();
- }
- }
-
- private void setMessageCallbackOnSession() {
- try {
- Assertions.checkNotNull(currentSession)
- .setMessageReceivedCallbacks(EXOPLAYER_CAST_NAMESPACE, messageReceivedCallback);
- expectedInitialStateUpdateSequence = send(new ExoCastMessage.OnClientConnected());
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
-
- /** Listens for Cast session state changes. */
- private class CastSessionListener implements SessionManagerListener {
-
- @Override
- public void onSessionStarting(CastSession castSession) {}
-
- @Override
- public void onSessionStarted(CastSession castSession, String sessionId) {
- setCastSession(castSession);
- }
-
- @Override
- public void onSessionStartFailed(CastSession castSession, int error) {}
-
- @Override
- public void onSessionEnding(CastSession castSession) {}
-
- @Override
- public void onSessionEnded(CastSession castSession, int error) {
- setCastSession(null);
- }
-
- @Override
- public void onSessionResuming(CastSession castSession, String sessionId) {}
-
- @Override
- public void onSessionResumed(CastSession castSession, boolean wasSuspended) {
- setCastSession(castSession);
- }
-
- @Override
- public void onSessionResumeFailed(CastSession castSession, int error) {}
-
- @Override
- public void onSessionSuspended(CastSession castSession, int reason) {
- setCastSession(null);
- }
- }
-
- private class CastMessageCallback implements Cast.MessageReceivedCallback {
-
- @Override
- public void onMessageReceived(CastDevice castDevice, String namespace, String message) {
- if (!EXOPLAYER_CAST_NAMESPACE.equals(namespace)) {
- // Non-matching namespace. Ignore.
- Log.e(TAG, String.format("Unrecognized namespace: '%s'.", namespace));
- return;
- }
- try {
- ReceiverAppStateUpdate receivedUpdate = ReceiverAppStateUpdate.fromJsonMessage(message);
- if (expectedInitialStateUpdateSequence == SEQUENCE_NUMBER_UNSET
- || receivedUpdate.sequenceNumber >= expectedInitialStateUpdateSequence) {
- stateListener.onStateUpdateFromReceiverApp(receivedUpdate);
- if (expectedInitialStateUpdateSequence != SEQUENCE_NUMBER_UNSET) {
- expectedInitialStateUpdateSequence = SEQUENCE_NUMBER_UNSET;
- stateListener.onCastSessionAvailable();
- }
- }
- } catch (JSONException e) {
- Log.e(TAG, "Error while parsing state update from receiver: ", e);
- }
- }
- }
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java
deleted file mode 100644
index 36173bfc5d..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastConstants.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-/** Defines constants used by the Cast extension. */
-public final class ExoCastConstants {
-
- private ExoCastConstants() {}
-
- public static final int PROTOCOL_VERSION = 0;
-
- // String representations.
-
- public static final String STR_STATE_IDLE = "IDLE";
- public static final String STR_STATE_BUFFERING = "BUFFERING";
- public static final String STR_STATE_READY = "READY";
- public static final String STR_STATE_ENDED = "ENDED";
-
- public static final String STR_REPEAT_MODE_OFF = "OFF";
- public static final String STR_REPEAT_MODE_ONE = "ONE";
- public static final String STR_REPEAT_MODE_ALL = "ALL";
-
- public static final String STR_DISCONTINUITY_REASON_PERIOD_TRANSITION = "PERIOD_TRANSITION";
- public static final String STR_DISCONTINUITY_REASON_SEEK = "SEEK";
- public static final String STR_DISCONTINUITY_REASON_SEEK_ADJUSTMENT = "SEEK_ADJUSTMENT";
- public static final String STR_DISCONTINUITY_REASON_AD_INSERTION = "AD_INSERTION";
- public static final String STR_DISCONTINUITY_REASON_INTERNAL = "INTERNAL";
-
- public static final String STR_SELECTION_FLAG_DEFAULT = "DEFAULT";
- public static final String STR_SELECTION_FLAG_FORCED = "FORCED";
- public static final String STR_SELECTION_FLAG_AUTOSELECT = "AUTOSELECT";
-
- // Methods.
-
- public static final String METHOD_BASE = "player.";
-
- public static final String METHOD_ON_CLIENT_CONNECTED = METHOD_BASE + "onClientConnected";
- public static final String METHOD_ADD_ITEMS = METHOD_BASE + "addItems";
- public static final String METHOD_MOVE_ITEM = METHOD_BASE + "moveItem";
- public static final String METHOD_PREPARE = METHOD_BASE + "prepare";
- public static final String METHOD_REMOVE_ITEMS = METHOD_BASE + "removeItems";
- public static final String METHOD_SET_PLAY_WHEN_READY = METHOD_BASE + "setPlayWhenReady";
- public static final String METHOD_SET_REPEAT_MODE = METHOD_BASE + "setRepeatMode";
- public static final String METHOD_SET_SHUFFLE_MODE_ENABLED =
- METHOD_BASE + "setShuffleModeEnabled";
- public static final String METHOD_SEEK_TO = METHOD_BASE + "seekTo";
- public static final String METHOD_SET_PLAYBACK_PARAMETERS = METHOD_BASE + "setPlaybackParameters";
- public static final String METHOD_SET_TRACK_SELECTION_PARAMETERS =
- METHOD_BASE + ".setTrackSelectionParameters";
- public static final String METHOD_STOP = METHOD_BASE + "stop";
-
- // JSON message keys.
-
- public static final String KEY_ARGS = "args";
- public static final String KEY_DEFAULT_START_POSITION_US = "defaultStartPositionUs";
- public static final String KEY_DESCRIPTION = "description";
- public static final String KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS =
- "disabledTextTrackSelectionFlags";
- public static final String KEY_DISCONTINUITY_REASON = "discontinuityReason";
- public static final String KEY_DRM_SCHEMES = "drmSchemes";
- public static final String KEY_DURATION_US = "durationUs";
- public static final String KEY_END_POSITION_US = "endPositionUs";
- public static final String KEY_ERROR_MESSAGE = "error";
- public static final String KEY_ID = "id";
- public static final String KEY_INDEX = "index";
- public static final String KEY_IS_DYNAMIC = "isDynamic";
- public static final String KEY_IS_LOADING = "isLoading";
- public static final String KEY_IS_SEEKABLE = "isSeekable";
- public static final String KEY_ITEMS = "items";
- public static final String KEY_LICENSE_SERVER = "licenseServer";
- public static final String KEY_MEDIA = "media";
- public static final String KEY_MEDIA_ITEMS_INFO = "mediaItemsInfo";
- public static final String KEY_MEDIA_QUEUE = "mediaQueue";
- public static final String KEY_METHOD = "method";
- public static final String KEY_MIME_TYPE = "mimeType";
- public static final String KEY_PERIOD_ID = "periodId";
- public static final String KEY_PERIODS = "periods";
- public static final String KEY_PITCH = "pitch";
- public static final String KEY_PLAY_WHEN_READY = "playWhenReady";
- public static final String KEY_PLAYBACK_PARAMETERS = "playbackParameters";
- public static final String KEY_PLAYBACK_POSITION = "playbackPosition";
- public static final String KEY_PLAYBACK_STATE = "playbackState";
- public static final String KEY_POSITION_IN_FIRST_PERIOD_US = "positionInFirstPeriodUs";
- public static final String KEY_POSITION_MS = "positionMs";
- public static final String KEY_PREFERRED_AUDIO_LANGUAGE = "preferredAudioLanguage";
- public static final String KEY_PREFERRED_TEXT_LANGUAGE = "preferredTextLanguage";
- public static final String KEY_PROTOCOL_VERSION = "protocolVersion";
- public static final String KEY_REPEAT_MODE = "repeatMode";
- public static final String KEY_REQUEST_HEADERS = "requestHeaders";
- public static final String KEY_RESET = "reset";
- public static final String KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE =
- "selectUndeterminedTextLanguage";
- public static final String KEY_SEQUENCE_NUMBER = "sequenceNumber";
- public static final String KEY_SHUFFLE_MODE_ENABLED = "shuffleModeEnabled";
- public static final String KEY_SHUFFLE_ORDER = "shuffleOrder";
- public static final String KEY_SKIP_SILENCE = "skipSilence";
- public static final String KEY_SPEED = "speed";
- public static final String KEY_START_POSITION_US = "startPositionUs";
- public static final String KEY_TITLE = "title";
- public static final String KEY_TRACK_SELECTION_PARAMETERS = "trackSelectionParameters";
- public static final String KEY_URI = "uri";
- public static final String KEY_UUID = "uuid";
- public static final String KEY_UUIDS = "uuids";
- public static final String KEY_WINDOW_DURATION_US = "windowDurationUs";
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastMessage.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastMessage.java
deleted file mode 100644
index 1529e9f5ac..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastMessage.java
+++ /dev/null
@@ -1,474 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ARGS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DESCRIPTION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DRM_SCHEMES;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_END_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_INDEX;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ITEMS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_LICENSE_SERVER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MEDIA;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_METHOD;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MIME_TYPE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PITCH;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAY_WHEN_READY;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_POSITION_MS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_AUDIO_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PROTOCOL_VERSION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_REPEAT_MODE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_REQUEST_HEADERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_RESET;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SEQUENCE_NUMBER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_MODE_ENABLED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_ORDER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SKIP_SILENCE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SPEED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_START_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_TITLE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_URI;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUIDS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_ADD_ITEMS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_MOVE_ITEM;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_ON_CLIENT_CONNECTED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_PREPARE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_REMOVE_ITEMS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SEEK_TO;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_PLAYBACK_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_PLAY_WHEN_READY;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_REPEAT_MODE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_SHUFFLE_MODE_ENABLED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_TRACK_SELECTION_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_STOP;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.PROTOCOL_VERSION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_ALL;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_OFF;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_ONE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_SELECTION_FLAG_AUTOSELECT;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_SELECTION_FLAG_DEFAULT;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_SELECTION_FLAG_FORCED;
-
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.ext.cast.MediaItem.UriBundle;
-import com.google.android.exoplayer2.source.ShuffleOrder;
-import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-// TODO(Internal b/118432277): Evaluate using a proto for sending to the receiver app.
-/** A serializable message for operating a media player. */
-public abstract class ExoCastMessage {
-
- /** Notifies the receiver app of the connection of a sender app to the message bus. */
- public static final class OnClientConnected extends ExoCastMessage {
-
- public OnClientConnected() {
- super(METHOD_ON_CLIENT_CONNECTED);
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() {
- // No arguments needed.
- return new JSONObject();
- }
- }
-
- /** Transitions the player out of {@link Player#STATE_IDLE}. */
- public static final class Prepare extends ExoCastMessage {
-
- public Prepare() {
- super(METHOD_PREPARE);
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() {
- // No arguments needed.
- return new JSONObject();
- }
- }
-
- /** Transitions the player to {@link Player#STATE_IDLE} and optionally resets its state. */
- public static final class Stop extends ExoCastMessage {
-
- /** Whether the player state should be reset. */
- public final boolean reset;
-
- public Stop(boolean reset) {
- super(METHOD_STOP);
- this.reset = reset;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- return new JSONObject().put(KEY_RESET, reset);
- }
- }
-
- /** Adds items to a media player queue. */
- public static final class AddItems extends ExoCastMessage {
-
- /**
- * The index at which the {@link #items} should be inserted. If {@link C#INDEX_UNSET}, the items
- * are appended to the queue.
- */
- public final int index;
- /** The {@link MediaItem items} to add to the media queue. */
- public final List items;
- /**
- * The shuffle order to use for the media queue that results of adding the items to the queue.
- */
- public final ShuffleOrder shuffleOrder;
-
- /**
- * @param index See {@link #index}.
- * @param items See {@link #items}.
- * @param shuffleOrder See {@link #shuffleOrder}.
- */
- public AddItems(int index, List items, ShuffleOrder shuffleOrder) {
- super(METHOD_ADD_ITEMS);
- this.index = index;
- this.items = Collections.unmodifiableList(new ArrayList<>(items));
- this.shuffleOrder = shuffleOrder;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- JSONObject arguments =
- new JSONObject()
- .put(KEY_ITEMS, getItemsAsJsonArray())
- .put(KEY_SHUFFLE_ORDER, getShuffleOrderAsJson(shuffleOrder));
- maybePutValue(arguments, KEY_INDEX, index, C.INDEX_UNSET);
- return arguments;
- }
-
- private JSONArray getItemsAsJsonArray() throws JSONException {
- JSONArray result = new JSONArray();
- for (MediaItem item : items) {
- result.put(mediaItemAsJsonObject(item));
- }
- return result;
- }
- }
-
- /** Moves an item in a player media queue. */
- public static final class MoveItem extends ExoCastMessage {
-
- /** The {@link MediaItem#uuid} of the item to move. */
- public final UUID uuid;
- /** The index in the queue to which the item should be moved. */
- public final int index;
- /** The shuffle order to use for the media queue that results of moving the item. */
- public ShuffleOrder shuffleOrder;
-
- /**
- * @param uuid See {@link #uuid}.
- * @param index See {@link #index}.
- * @param shuffleOrder See {@link #shuffleOrder}.
- */
- public MoveItem(UUID uuid, int index, ShuffleOrder shuffleOrder) {
- super(METHOD_MOVE_ITEM);
- this.uuid = uuid;
- this.index = index;
- this.shuffleOrder = shuffleOrder;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- return new JSONObject()
- .put(KEY_UUID, uuid)
- .put(KEY_INDEX, index)
- .put(KEY_SHUFFLE_ORDER, getShuffleOrderAsJson(shuffleOrder));
- }
- }
-
- /** Removes items from a player queue. */
- public static final class RemoveItems extends ExoCastMessage {
-
- /** The {@link MediaItem#uuid} of the items to remove from the queue. */
- public final List uuids;
-
- /** @param uuids See {@link #uuids}. */
- public RemoveItems(List uuids) {
- super(METHOD_REMOVE_ITEMS);
- this.uuids = Collections.unmodifiableList(new ArrayList<>(uuids));
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- return new JSONObject().put(KEY_UUIDS, new JSONArray(uuids));
- }
- }
-
- /** See {@link Player#setPlayWhenReady(boolean)}. */
- public static final class SetPlayWhenReady extends ExoCastMessage {
-
- /** The {@link Player#setPlayWhenReady(boolean) playWhenReady} value to set. */
- public final boolean playWhenReady;
-
- /** @param playWhenReady See {@link #playWhenReady}. */
- public SetPlayWhenReady(boolean playWhenReady) {
- super(METHOD_SET_PLAY_WHEN_READY);
- this.playWhenReady = playWhenReady;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- return new JSONObject().put(KEY_PLAY_WHEN_READY, playWhenReady);
- }
- }
-
- /**
- * Sets the repeat mode of the media player.
- *
- * @see Player#setRepeatMode(int)
- */
- public static final class SetRepeatMode extends ExoCastMessage {
-
- /** The {@link Player#setRepeatMode(int) repeatMode} to set. */
- @Player.RepeatMode public final int repeatMode;
-
- /** @param repeatMode See {@link #repeatMode}. */
- public SetRepeatMode(@Player.RepeatMode int repeatMode) {
- super(METHOD_SET_REPEAT_MODE);
- this.repeatMode = repeatMode;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- return new JSONObject().put(KEY_REPEAT_MODE, repeatModeToString(repeatMode));
- }
-
- private static String repeatModeToString(@Player.RepeatMode int repeatMode) {
- switch (repeatMode) {
- case REPEAT_MODE_OFF:
- return STR_REPEAT_MODE_OFF;
- case REPEAT_MODE_ONE:
- return STR_REPEAT_MODE_ONE;
- case REPEAT_MODE_ALL:
- return STR_REPEAT_MODE_ALL;
- default:
- throw new AssertionError("Illegal repeat mode: " + repeatMode);
- }
- }
- }
-
- /**
- * Enables and disables shuffle mode in the media player.
- *
- * @see Player#setShuffleModeEnabled(boolean)
- */
- public static final class SetShuffleModeEnabled extends ExoCastMessage {
-
- /** The {@link Player#setShuffleModeEnabled(boolean) shuffleModeEnabled} value to set. */
- public boolean shuffleModeEnabled;
-
- /** @param shuffleModeEnabled See {@link #shuffleModeEnabled}. */
- public SetShuffleModeEnabled(boolean shuffleModeEnabled) {
- super(METHOD_SET_SHUFFLE_MODE_ENABLED);
- this.shuffleModeEnabled = shuffleModeEnabled;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- return new JSONObject().put(KEY_SHUFFLE_MODE_ENABLED, shuffleModeEnabled);
- }
- }
-
- /** See {@link Player#seekTo(int, long)}. */
- public static final class SeekTo extends ExoCastMessage {
-
- /** The {@link MediaItem#uuid} of the item to seek to. */
- public final UUID uuid;
- /**
- * The seek position in milliseconds in the specified item. If {@link C#TIME_UNSET}, the target
- * position is the item's default position.
- */
- public final long positionMs;
-
- /**
- * @param uuid See {@link #uuid}.
- * @param positionMs See {@link #positionMs}.
- */
- public SeekTo(UUID uuid, long positionMs) {
- super(METHOD_SEEK_TO);
- this.uuid = uuid;
- this.positionMs = positionMs;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- JSONObject result = new JSONObject().put(KEY_UUID, uuid);
- ExoCastMessage.maybePutValue(result, KEY_POSITION_MS, positionMs, C.TIME_UNSET);
- return result;
- }
- }
-
- /** See {@link Player#setPlaybackParameters(PlaybackParameters)}. */
- public static final class SetPlaybackParameters extends ExoCastMessage {
-
- /** The {@link Player#setPlaybackParameters(PlaybackParameters) parameters} to set. */
- public final PlaybackParameters playbackParameters;
-
- /** @param playbackParameters See {@link #playbackParameters}. */
- public SetPlaybackParameters(PlaybackParameters playbackParameters) {
- super(METHOD_SET_PLAYBACK_PARAMETERS);
- this.playbackParameters = playbackParameters;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- return new JSONObject()
- .put(KEY_SPEED, playbackParameters.speed)
- .put(KEY_PITCH, playbackParameters.pitch)
- .put(KEY_SKIP_SILENCE, playbackParameters.skipSilence);
- }
- }
-
- /** See {@link ExoCastPlayer#setTrackSelectionParameters(TrackSelectionParameters)}. */
- public static final class SetTrackSelectionParameters extends ExoCastMessage {
-
- /**
- * The {@link ExoCastPlayer#setTrackSelectionParameters(TrackSelectionParameters) parameters} to
- * set
- */
- public final TrackSelectionParameters trackSelectionParameters;
-
- public SetTrackSelectionParameters(TrackSelectionParameters trackSelectionParameters) {
- super(METHOD_SET_TRACK_SELECTION_PARAMETERS);
- this.trackSelectionParameters = trackSelectionParameters;
- }
-
- @Override
- protected JSONObject getArgumentsAsJsonObject() throws JSONException {
- JSONArray disabledTextSelectionFlagsJson = new JSONArray();
- int disabledSelectionFlags = trackSelectionParameters.disabledTextTrackSelectionFlags;
- if ((disabledSelectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) {
- disabledTextSelectionFlagsJson.put(STR_SELECTION_FLAG_AUTOSELECT);
- }
- if ((disabledSelectionFlags & C.SELECTION_FLAG_FORCED) != 0) {
- disabledTextSelectionFlagsJson.put(STR_SELECTION_FLAG_FORCED);
- }
- if ((disabledSelectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) {
- disabledTextSelectionFlagsJson.put(STR_SELECTION_FLAG_DEFAULT);
- }
- return new JSONObject()
- .put(KEY_PREFERRED_AUDIO_LANGUAGE, trackSelectionParameters.preferredAudioLanguage)
- .put(KEY_PREFERRED_TEXT_LANGUAGE, trackSelectionParameters.preferredTextLanguage)
- .put(KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS, disabledTextSelectionFlagsJson)
- .put(
- KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE,
- trackSelectionParameters.selectUndeterminedTextLanguage);
- }
- }
-
- public final String method;
-
- /**
- * Creates a message with the given method.
- *
- * @param method The method of the message.
- */
- protected ExoCastMessage(String method) {
- this.method = method;
- }
-
- /**
- * Returns a string containing a JSON representation of this message.
- *
- * @param sequenceNumber The sequence number to associate with this message.
- * @return A string containing a JSON representation of this message.
- */
- public final String toJsonString(long sequenceNumber) {
- try {
- JSONObject message =
- new JSONObject()
- .put(KEY_PROTOCOL_VERSION, PROTOCOL_VERSION)
- .put(KEY_METHOD, method)
- .put(KEY_SEQUENCE_NUMBER, sequenceNumber)
- .put(KEY_ARGS, getArgumentsAsJsonObject());
- return message.toString();
- } catch (JSONException e) {
- throw new AssertionError(e);
- }
- }
-
- /** Returns a {@link JSONObject} representation of the given item. */
- protected static JSONObject mediaItemAsJsonObject(MediaItem item) throws JSONException {
- JSONObject itemAsJson = new JSONObject();
- itemAsJson.put(KEY_UUID, item.uuid);
- itemAsJson.put(KEY_TITLE, item.title);
- itemAsJson.put(KEY_DESCRIPTION, item.description);
- itemAsJson.put(KEY_MEDIA, uriBundleAsJsonObject(item.media));
- // TODO(Internal b/118431961): Add attachment management.
-
- JSONArray drmSchemesAsJson = new JSONArray();
- for (MediaItem.DrmScheme drmScheme : item.drmSchemes) {
- JSONObject drmSchemeAsJson = new JSONObject();
- drmSchemeAsJson.put(KEY_UUID, drmScheme.uuid);
- if (drmScheme.licenseServer != null) {
- drmSchemeAsJson.put(KEY_LICENSE_SERVER, uriBundleAsJsonObject(drmScheme.licenseServer));
- }
- drmSchemesAsJson.put(drmSchemeAsJson);
- }
- itemAsJson.put(KEY_DRM_SCHEMES, drmSchemesAsJson);
- maybePutValue(itemAsJson, KEY_START_POSITION_US, item.startPositionUs, C.TIME_UNSET);
- maybePutValue(itemAsJson, KEY_END_POSITION_US, item.endPositionUs, C.TIME_UNSET);
- itemAsJson.put(KEY_MIME_TYPE, item.mimeType);
- return itemAsJson;
- }
-
- /** Returns a {@link JSONObject JSON object} containing the arguments of the message. */
- protected abstract JSONObject getArgumentsAsJsonObject() throws JSONException;
-
- /** Returns a JSON representation of the given {@link UriBundle}. */
- protected static JSONObject uriBundleAsJsonObject(UriBundle uriBundle) throws JSONException {
- return new JSONObject()
- .put(KEY_URI, uriBundle.uri)
- .put(KEY_REQUEST_HEADERS, new JSONObject(uriBundle.requestHeaders));
- }
-
- private static JSONArray getShuffleOrderAsJson(ShuffleOrder shuffleOrder) {
- JSONArray shuffleOrderJson = new JSONArray();
- int index = shuffleOrder.getFirstIndex();
- while (index != C.INDEX_UNSET) {
- shuffleOrderJson.put(index);
- index = shuffleOrder.getNextIndex(index);
- }
- return shuffleOrderJson;
- }
-
- private static void maybePutValue(JSONObject target, String key, long value, long unsetValue)
- throws JSONException {
- if (value != unsetValue) {
- target.put(key, value);
- }
- }
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastOptionsProvider.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastOptionsProvider.java
deleted file mode 100644
index 56b5d3cc8c..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastOptionsProvider.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import android.content.Context;
-import androidx.annotation.Nullable;
-import com.google.android.gms.cast.framework.CastOptions;
-import com.google.android.gms.cast.framework.OptionsProvider;
-import com.google.android.gms.cast.framework.SessionProvider;
-import java.util.List;
-
-/** Cast options provider to target ExoPlayer's custom receiver app. */
-public final class ExoCastOptionsProvider implements OptionsProvider {
-
- public static final String RECEIVER_ID = "365DCC88";
-
- @Override
- public CastOptions getCastOptions(Context context) {
- return new CastOptions.Builder().setReceiverApplicationId(RECEIVER_ID).build();
- }
-
- @Override
- @Nullable
- public List getAdditionalSessionProviders(Context context) {
- return null;
- }
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastPlayer.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastPlayer.java
deleted file mode 100644
index e24970ba0d..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastPlayer.java
+++ /dev/null
@@ -1,958 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import android.os.Looper;
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.BasePlayer;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.ExoPlaybackException;
-import com.google.android.exoplayer2.IllegalSeekPositionException;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.ext.cast.ExoCastMessage.AddItems;
-import com.google.android.exoplayer2.ext.cast.ExoCastMessage.MoveItem;
-import com.google.android.exoplayer2.ext.cast.ExoCastMessage.RemoveItems;
-import com.google.android.exoplayer2.ext.cast.ExoCastMessage.SetRepeatMode;
-import com.google.android.exoplayer2.ext.cast.ExoCastMessage.SetShuffleModeEnabled;
-import com.google.android.exoplayer2.ext.cast.ExoCastMessage.SetTrackSelectionParameters;
-import com.google.android.exoplayer2.ext.cast.ExoCastTimeline.PeriodUid;
-import com.google.android.exoplayer2.source.ShuffleOrder;
-import com.google.android.exoplayer2.source.TrackGroupArray;
-import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
-import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Clock;
-import com.google.android.exoplayer2.util.Util;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CopyOnWriteArrayList;
-import org.checkerframework.checker.nullness.compatqual.NullableType;
-
-/**
- * Plays media in a Cast receiver app that implements the ExoCast message protocol.
- *
- * The ExoCast communication protocol consists in exchanging serialized {@link ExoCastMessage
- * ExoCastMessages} and {@link ReceiverAppStateUpdate receiver app state updates}.
- *
- *
All methods in this class must be invoked on the main thread. Operations that change the state
- * of the receiver app are masked locally as if their effect was immediate in the receiver app.
- *
- *
Methods that change the state of the player must only be invoked when a session is available,
- * according to {@link CastSessionManager#isCastSessionAvailable()}.
- */
-public final class ExoCastPlayer extends BasePlayer {
-
- private static final String TAG = "ExoCastPlayer";
-
- private static final int RENDERER_COUNT = 4;
- private static final int RENDERER_INDEX_VIDEO = 0;
- private static final int RENDERER_INDEX_AUDIO = 1;
- private static final int RENDERER_INDEX_TEXT = 2;
- private static final int RENDERER_INDEX_METADATA = 3;
-
- private final Clock clock;
- private final CastSessionManager castSessionManager;
- private final CopyOnWriteArrayList listeners;
- private final ArrayList notificationsBatch;
- private final ArrayDeque ongoingNotificationsTasks;
- private final Timeline.Period scratchPeriod;
- @Nullable private SessionAvailabilityListener sessionAvailabilityListener;
-
- // Player state.
-
- private final List mediaItems;
- private final StateHolder currentTimeline;
- private ShuffleOrder currentShuffleOrder;
-
- private final StateHolder playbackState;
- private final StateHolder playWhenReady;
- private final StateHolder repeatMode;
- private final StateHolder shuffleModeEnabled;
- private final StateHolder isLoading;
- private final StateHolder playbackParameters;
- private final StateHolder trackselectionParameters;
- private final StateHolder currentTrackGroups;
- private final StateHolder currentTrackSelections;
- private final StateHolder<@NullableType Object> currentManifest;
- private final StateHolder<@NullableType PeriodUid> currentPeriodUid;
- private final StateHolder playbackPositionMs;
- private final HashMap currentMediaItemInfoMap;
- private long lastPlaybackPositionChangeTimeMs;
- @Nullable private ExoPlaybackException playbackError;
-
- /**
- * Creates an instance using the system clock for calculating time deltas.
- *
- * @param castSessionManagerFactory Factory to create the {@link CastSessionManager}.
- */
- public ExoCastPlayer(CastSessionManager.Factory castSessionManagerFactory) {
- this(castSessionManagerFactory, Clock.DEFAULT);
- }
-
- /**
- * Creates an instance using a custom {@link Clock} implementation.
- *
- * @param castSessionManagerFactory Factory to create the {@link CastSessionManager}.
- * @param clock The clock to use for time delta calculations.
- */
- public ExoCastPlayer(CastSessionManager.Factory castSessionManagerFactory, Clock clock) {
- this.clock = clock;
- castSessionManager = castSessionManagerFactory.create(new SessionManagerStateListener());
- listeners = new CopyOnWriteArrayList<>();
- notificationsBatch = new ArrayList<>();
- ongoingNotificationsTasks = new ArrayDeque<>();
- scratchPeriod = new Timeline.Period();
- mediaItems = new ArrayList<>();
- currentShuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length= */ mediaItems.size());
- playbackState = new StateHolder<>(STATE_IDLE);
- playWhenReady = new StateHolder<>(false);
- repeatMode = new StateHolder<>(REPEAT_MODE_OFF);
- shuffleModeEnabled = new StateHolder<>(false);
- isLoading = new StateHolder<>(false);
- playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
- trackselectionParameters = new StateHolder<>(TrackSelectionParameters.DEFAULT);
- currentTrackGroups = new StateHolder<>(TrackGroupArray.EMPTY);
- currentTrackSelections = new StateHolder<>(new TrackSelectionArray(null, null, null, null));
- currentManifest = new StateHolder<>(null);
- currentTimeline = new StateHolder<>(ExoCastTimeline.EMPTY);
- playbackPositionMs = new StateHolder<>(0L);
- currentPeriodUid = new StateHolder<>(null);
- currentMediaItemInfoMap = new HashMap<>();
- castSessionManager.start();
- }
-
- /** Returns whether a Cast session is available. */
- public boolean isCastSessionAvailable() {
- return castSessionManager.isCastSessionAvailable();
- }
-
- /**
- * Sets a listener for updates on the Cast session availability.
- *
- * @param listener The {@link SessionAvailabilityListener}.
- */
- public void setSessionAvailabilityListener(@Nullable SessionAvailabilityListener listener) {
- sessionAvailabilityListener = listener;
- }
-
- /**
- * Prepares the player for playback.
- *
- * Sends a preparation message to the receiver. If the player is in {@link #STATE_IDLE},
- * updates the timeline with the media queue contents.
- */
- public void prepare() {
- long sequence = castSessionManager.send(new ExoCastMessage.Prepare());
- if (playbackState.value == STATE_IDLE) {
- playbackState.sequence = sequence;
- setPlaybackStateInternal(mediaItems.isEmpty() ? STATE_ENDED : STATE_BUFFERING);
- if (!currentTimeline.value.representsMediaQueue(
- mediaItems, currentMediaItemInfoMap, currentShuffleOrder)) {
- updateTimelineInternal(TIMELINE_CHANGE_REASON_PREPARED);
- }
- }
- flushNotifications();
- }
-
- /**
- * Returns the item at the given index.
- *
- * @param index The index of the item to retrieve.
- * @return The item at the given index.
- */
- public MediaItem getQueueItem(int index) {
- return mediaItems.get(index);
- }
-
- /**
- * Equivalent to {@link #addItemsToQueue(int, MediaItem...) addItemsToQueue(C.INDEX_UNSET,
- * items)}.
- */
- public void addItemsToQueue(MediaItem... items) {
- addItemsToQueue(C.INDEX_UNSET, items);
- }
-
- /**
- * Adds the given sequence of items to the queue at the given position, so that the first of
- * {@code items} is placed at the given index.
- *
- *
This method discards {@code items} with a uuid that already appears in the media queue. This
- * method does nothing if {@code items} contains no new items.
- *
- * @param optionalIndex The index at which {@code items} will be inserted. If {@link
- * C#INDEX_UNSET} is passed, the items are appended to the media queue.
- * @param items The sequence of items to append. {@code items} must not contain items with
- * matching uuids.
- * @throws IllegalArgumentException If two or more elements in {@code items} contain matching
- * uuids.
- */
- public void addItemsToQueue(int optionalIndex, MediaItem... items) {
- // Filter out items whose uuid already appears in the queue.
- ArrayList itemsToAdd = new ArrayList<>();
- HashSet addedUuids = new HashSet<>();
- for (MediaItem item : items) {
- Assertions.checkArgument(
- addedUuids.add(item.uuid), "Added items must contain distinct uuids");
- if (playbackState.value == STATE_IDLE
- || currentTimeline.value.getWindowIndexFromUuid(item.uuid) == C.INDEX_UNSET) {
- // Prevent adding items that exist in the timeline. If the player is not yet prepared,
- // ignore this check, since the timeline may not reflect the current media queue.
- // Preparation will filter any duplicates.
- itemsToAdd.add(item);
- }
- }
- if (itemsToAdd.isEmpty()) {
- return;
- }
-
- int normalizedIndex;
- if (optionalIndex != C.INDEX_UNSET) {
- normalizedIndex = optionalIndex;
- mediaItems.addAll(optionalIndex, itemsToAdd);
- } else {
- normalizedIndex = mediaItems.size();
- mediaItems.addAll(itemsToAdd);
- }
- currentShuffleOrder = currentShuffleOrder.cloneAndInsert(normalizedIndex, itemsToAdd.size());
- long sequence =
- castSessionManager.send(new AddItems(optionalIndex, itemsToAdd, currentShuffleOrder));
- if (playbackState.value != STATE_IDLE) {
- currentTimeline.sequence = sequence;
- updateTimelineInternal(TIMELINE_CHANGE_REASON_DYNAMIC);
- }
- flushNotifications();
- }
-
- /**
- * Moves an existing item within the queue.
- *
- * Calling this method is equivalent to removing the item at position {@code indexFrom} and
- * immediately inserting it at position {@code indexTo}. If the moved item is being played at the
- * moment of the invocation, playback will stick with the moved item.
- *
- * @param index The index of the item to move.
- * @param newIndex The index at which the item will be placed after this operation.
- */
- public void moveItemInQueue(int index, int newIndex) {
- MediaItem movedItem = mediaItems.remove(index);
- mediaItems.add(newIndex, movedItem);
- currentShuffleOrder =
- currentShuffleOrder
- .cloneAndRemove(index, index + 1)
- .cloneAndInsert(newIndex, /* insertionCount= */ 1);
- long sequence =
- castSessionManager.send(new MoveItem(movedItem.uuid, newIndex, currentShuffleOrder));
- if (playbackState.value != STATE_IDLE) {
- currentTimeline.sequence = sequence;
- updateTimelineInternal(TIMELINE_CHANGE_REASON_DYNAMIC);
- }
- flushNotifications();
- }
-
- /**
- * Removes an item from the queue.
- *
- * @param index The index of the item to remove from the queue.
- */
- public void removeItemFromQueue(int index) {
- removeRangeFromQueue(index, index + 1);
- }
-
- /**
- * Removes a range of items from the queue.
- *
- *
If the currently-playing item is removed, the playback position moves to the item following
- * the removed range. If no item follows the removed range, the position is set to the last item
- * in the queue and the player state transitions to {@link #STATE_ENDED}. Does nothing if an empty
- * range ({@code from == exclusiveTo}) is passed.
- *
- * @param indexFrom The inclusive index at which the range to remove starts.
- * @param indexExclusiveTo The exclusive index at which the range to remove ends.
- */
- public void removeRangeFromQueue(int indexFrom, int indexExclusiveTo) {
- UUID[] uuidsToRemove = new UUID[indexExclusiveTo - indexFrom];
- for (int i = 0; i < uuidsToRemove.length; i++) {
- uuidsToRemove[i] = mediaItems.get(i + indexFrom).uuid;
- }
-
- int windowIndexBeforeRemoval = getCurrentWindowIndex();
- boolean currentItemWasRemoved =
- windowIndexBeforeRemoval >= indexFrom && windowIndexBeforeRemoval < indexExclusiveTo;
- boolean shouldTransitionToEnded =
- currentItemWasRemoved && indexExclusiveTo == mediaItems.size();
-
- Util.removeRange(mediaItems, indexFrom, indexExclusiveTo);
- long sequence = castSessionManager.send(new RemoveItems(Arrays.asList(uuidsToRemove)));
- currentShuffleOrder = currentShuffleOrder.cloneAndRemove(indexFrom, indexExclusiveTo);
-
- if (playbackState.value != STATE_IDLE) {
- currentTimeline.sequence = sequence;
- updateTimelineInternal(TIMELINE_CHANGE_REASON_DYNAMIC);
- if (currentItemWasRemoved) {
- int newWindowIndex = Math.max(0, indexFrom - (shouldTransitionToEnded ? 1 : 0));
- PeriodUid periodUid =
- currentTimeline.value.isEmpty()
- ? null
- : (PeriodUid)
- currentTimeline.value.getPeriodPosition(
- window,
- scratchPeriod,
- newWindowIndex,
- /* windowPositionUs= */ C.TIME_UNSET)
- .first;
- currentPeriodUid.sequence = sequence;
- playbackPositionMs.sequence = sequence;
- setPlaybackPositionInternal(
- periodUid,
- /* positionMs= */ C.TIME_UNSET,
- /* discontinuityReason= */ DISCONTINUITY_REASON_SEEK);
- }
- playbackState.sequence = sequence;
- setPlaybackStateInternal(shouldTransitionToEnded ? STATE_ENDED : STATE_BUFFERING);
- }
- flushNotifications();
- }
-
- /** Removes all items in the queue. */
- public void clearQueue() {
- removeRangeFromQueue(0, getQueueSize());
- }
-
- /** Returns the number of items in this queue. */
- public int getQueueSize() {
- return mediaItems.size();
- }
-
- // Track selection.
-
- /**
- * Provides a set of constrains for the receiver app to execute track selection.
- *
- *
{@link TrackSelectionParameters} passed to this method may be {@link
- * TrackSelectionParameters#buildUpon() built upon} by this player as a result of a remote
- * operation, which means {@link TrackSelectionParameters} obtained from {@link
- * #getTrackSelectionParameters()} may have field differences with {@code parameters} passed to
- * this method. However, only fields modified remotely will present differences. Other fields will
- * remain unchanged.
- */
- public void setTrackSelectionParameters(TrackSelectionParameters trackselectionParameters) {
- this.trackselectionParameters.value = trackselectionParameters;
- this.trackselectionParameters.sequence =
- castSessionManager.send(new SetTrackSelectionParameters(trackselectionParameters));
- }
-
- /**
- * Retrieves the current {@link TrackSelectionParameters}. See {@link
- * #setTrackSelectionParameters(TrackSelectionParameters)}.
- */
- public TrackSelectionParameters getTrackSelectionParameters() {
- return trackselectionParameters.value;
- }
-
- // Player Implementation.
-
- @Override
- @Nullable
- public AudioComponent getAudioComponent() {
- // TODO: Implement volume controls using the audio component.
- return null;
- }
-
- @Override
- @Nullable
- public VideoComponent getVideoComponent() {
- return null;
- }
-
- @Override
- @Nullable
- public TextComponent getTextComponent() {
- return null;
- }
-
- @Override
- @Nullable
- public MetadataComponent getMetadataComponent() {
- return null;
- }
-
- @Override
- public Looper getApplicationLooper() {
- return Looper.getMainLooper();
- }
-
- @Override
- public void addListener(EventListener listener) {
- listeners.addIfAbsent(new ListenerHolder(listener));
- }
-
- @Override
- public void removeListener(EventListener listener) {
- for (ListenerHolder listenerHolder : listeners) {
- if (listenerHolder.listener.equals(listener)) {
- listenerHolder.release();
- listeners.remove(listenerHolder);
- }
- }
- }
-
- @Override
- @Player.State
- public int getPlaybackState() {
- return playbackState.value;
- }
-
- @Nullable
- @Override
- public ExoPlaybackException getPlaybackError() {
- return playbackError;
- }
-
- @Override
- public void setPlayWhenReady(boolean playWhenReady) {
- this.playWhenReady.sequence =
- castSessionManager.send(new ExoCastMessage.SetPlayWhenReady(playWhenReady));
- // Take a snapshot of the playback position before pausing to ensure future calculations are
- // correct.
- setPlaybackPositionInternal(
- currentPeriodUid.value, getCurrentPosition(), /* discontinuityReason= */ null);
- setPlayWhenReadyInternal(playWhenReady);
- flushNotifications();
- }
-
- @Override
- public boolean getPlayWhenReady() {
- return playWhenReady.value;
- }
-
- @Override
- public void setRepeatMode(@RepeatMode int repeatMode) {
- this.repeatMode.sequence = castSessionManager.send(new SetRepeatMode(repeatMode));
- setRepeatModeInternal(repeatMode);
- flushNotifications();
- }
-
- @Override
- @RepeatMode
- public int getRepeatMode() {
- return repeatMode.value;
- }
-
- @Override
- public void setShuffleModeEnabled(boolean shuffleModeEnabled) {
- this.shuffleModeEnabled.sequence =
- castSessionManager.send(new SetShuffleModeEnabled(shuffleModeEnabled));
- setShuffleModeEnabledInternal(shuffleModeEnabled);
- flushNotifications();
- }
-
- @Override
- public boolean getShuffleModeEnabled() {
- return shuffleModeEnabled.value;
- }
-
- @Override
- public boolean isLoading() {
- return isLoading.value;
- }
-
- @Override
- public void seekTo(int windowIndex, long positionMs) {
- if (mediaItems.isEmpty()) {
- // TODO: Handle seeking in empty timeline.
- setPlaybackPositionInternal(/* periodUid= */ null, 0, DISCONTINUITY_REASON_SEEK);
- return;
- } else if (windowIndex >= mediaItems.size()) {
- throw new IllegalSeekPositionException(currentTimeline.value, windowIndex, positionMs);
- }
- long sequence =
- castSessionManager.send(
- new ExoCastMessage.SeekTo(mediaItems.get(windowIndex).uuid, positionMs));
-
- currentPeriodUid.sequence = sequence;
- playbackPositionMs.sequence = sequence;
-
- PeriodUid periodUid =
- (PeriodUid)
- currentTimeline.value.getPeriodPosition(
- window, scratchPeriod, windowIndex, C.msToUs(positionMs))
- .first;
- setPlaybackPositionInternal(periodUid, positionMs, DISCONTINUITY_REASON_SEEK);
- if (playbackState.value != STATE_IDLE) {
- playbackState.sequence = sequence;
- setPlaybackStateInternal(STATE_BUFFERING);
- }
- flushNotifications();
- }
-
- @Override
- public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
- playbackParameters =
- playbackParameters != null ? playbackParameters : PlaybackParameters.DEFAULT;
- this.playbackParameters.value = playbackParameters;
- this.playbackParameters.sequence =
- castSessionManager.send(new ExoCastMessage.SetPlaybackParameters(playbackParameters));
- this.playbackParameters.value = playbackParameters;
- // Note: This method, unlike others, does not immediately notify the change. See the Player
- // interface for more information.
- }
-
- @Override
- public PlaybackParameters getPlaybackParameters() {
- return playbackParameters.value;
- }
-
- @Override
- public void stop(boolean reset) {
- long sequence = castSessionManager.send(new ExoCastMessage.Stop(reset));
- playbackState.sequence = sequence;
- setPlaybackStateInternal(STATE_IDLE);
- if (reset) {
- currentTimeline.sequence = sequence;
- mediaItems.clear();
- currentShuffleOrder = new ShuffleOrder.DefaultShuffleOrder(/* length =*/ 0);
- setPlaybackPositionInternal(
- /* periodUid= */ null, /* positionMs= */ 0, DISCONTINUITY_REASON_INTERNAL);
- updateTimelineInternal(TIMELINE_CHANGE_REASON_RESET);
- }
- flushNotifications();
- }
-
- @Override
- public void release() {
- setSessionAvailabilityListener(null);
- castSessionManager.stopTrackingSession();
- flushNotifications();
- }
-
- @Override
- public int getRendererCount() {
- return RENDERER_COUNT;
- }
-
- @Override
- public int getRendererType(int index) {
- switch (index) {
- case RENDERER_INDEX_VIDEO:
- return C.TRACK_TYPE_VIDEO;
- case RENDERER_INDEX_AUDIO:
- return C.TRACK_TYPE_AUDIO;
- case RENDERER_INDEX_TEXT:
- return C.TRACK_TYPE_TEXT;
- case RENDERER_INDEX_METADATA:
- return C.TRACK_TYPE_METADATA;
- default:
- throw new IndexOutOfBoundsException();
- }
- }
-
- @Override
- public TrackGroupArray getCurrentTrackGroups() {
- // TODO (Internal b/62080507): Implement using track information from currentMediaItemInfoMap.
- return currentTrackGroups.value;
- }
-
- @Override
- public TrackSelectionArray getCurrentTrackSelections() {
- // TODO (Internal b/62080507): Implement using track information from currentMediaItemInfoMap.
- return currentTrackSelections.value;
- }
-
- @Override
- @Nullable
- public Object getCurrentManifest() {
- // TODO (Internal b/62080507): Implement using track information from currentMediaItemInfoMap.
- return currentManifest.value;
- }
-
- @Override
- public Timeline getCurrentTimeline() {
- return currentTimeline.value;
- }
-
- @Override
- public int getCurrentPeriodIndex() {
- int periodIndex =
- currentPeriodUid.value == null
- ? C.INDEX_UNSET
- : currentTimeline.value.getIndexOfPeriod(currentPeriodUid.value);
- return periodIndex != C.INDEX_UNSET ? periodIndex : 0;
- }
-
- @Override
- public int getCurrentWindowIndex() {
- int windowIndex =
- currentPeriodUid.value == null
- ? C.INDEX_UNSET
- : currentTimeline.value.getWindowIndexContainingPeriod(currentPeriodUid.value);
- return windowIndex != C.INDEX_UNSET ? windowIndex : 0;
- }
-
- @Override
- public long getDuration() {
- return getContentDuration();
- }
-
- @Override
- public long getCurrentPosition() {
- return playbackPositionMs.value
- + (getPlaybackState() == STATE_READY && getPlayWhenReady()
- ? projectPlaybackTimeElapsedMs()
- : 0L);
- }
-
- @Override
- public long getBufferedPosition() {
- return getCurrentPosition();
- }
-
- @Override
- public long getTotalBufferedDuration() {
- return 0;
- }
-
- @Override
- public boolean isPlayingAd() {
- // TODO (Internal b/119293631): Add support for ads.
- return false;
- }
-
- @Override
- public int getCurrentAdGroupIndex() {
- return C.INDEX_UNSET;
- }
-
- @Override
- public int getCurrentAdIndexInAdGroup() {
- return C.INDEX_UNSET;
- }
-
- @Override
- public long getContentPosition() {
- return getCurrentPosition();
- }
-
- @Override
- public long getContentBufferedPosition() {
- return getCurrentPosition();
- }
-
- // Local state modifications.
-
- private void setPlayWhenReadyInternal(boolean playWhenReady) {
- if (this.playWhenReady.value != playWhenReady) {
- this.playWhenReady.value = playWhenReady;
- notificationsBatch.add(
- new ListenerNotificationTask(
- listener -> listener.onPlayerStateChanged(playWhenReady, playbackState.value)));
- }
- }
-
- private void setPlaybackStateInternal(int playbackState) {
- if (this.playbackState.value != playbackState) {
- if (this.playbackState.value == STATE_IDLE) {
- // We are transitioning out of STATE_IDLE. We clear any errors.
- setPlaybackErrorInternal(null);
- }
- this.playbackState.value = playbackState;
- notificationsBatch.add(
- new ListenerNotificationTask(
- listener -> listener.onPlayerStateChanged(playWhenReady.value, playbackState)));
- }
- }
-
- private void setRepeatModeInternal(int repeatMode) {
- if (this.repeatMode.value != repeatMode) {
- this.repeatMode.value = repeatMode;
- notificationsBatch.add(
- new ListenerNotificationTask(listener -> listener.onRepeatModeChanged(repeatMode)));
- }
- }
-
- private void setShuffleModeEnabledInternal(boolean shuffleModeEnabled) {
- if (this.shuffleModeEnabled.value != shuffleModeEnabled) {
- this.shuffleModeEnabled.value = shuffleModeEnabled;
- notificationsBatch.add(
- new ListenerNotificationTask(
- listener -> listener.onShuffleModeEnabledChanged(shuffleModeEnabled)));
- }
- }
-
- private void setIsLoadingInternal(boolean isLoading) {
- if (this.isLoading.value != isLoading) {
- this.isLoading.value = isLoading;
- notificationsBatch.add(
- new ListenerNotificationTask(listener -> listener.onLoadingChanged(isLoading)));
- }
- }
-
- private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) {
- if (!this.playbackParameters.value.equals(playbackParameters)) {
- this.playbackParameters.value = playbackParameters;
- notificationsBatch.add(
- new ListenerNotificationTask(
- listener -> listener.onPlaybackParametersChanged(playbackParameters)));
- }
- }
-
- private void setPlaybackErrorInternal(@Nullable String errorMessage) {
- if (errorMessage != null) {
- playbackError = ExoPlaybackException.createForRemote(errorMessage);
- notificationsBatch.add(
- new ListenerNotificationTask(
- listener -> listener.onPlayerError(Assertions.checkNotNull(playbackError))));
- } else {
- playbackError = null;
- }
- }
-
- private void setPlaybackPositionInternal(
- @Nullable PeriodUid periodUid, long positionMs, @Nullable Integer discontinuityReason) {
- currentPeriodUid.value = periodUid;
- if (periodUid == null) {
- positionMs = 0L;
- } else if (positionMs == C.TIME_UNSET) {
- int windowIndex = currentTimeline.value.getWindowIndexContainingPeriod(periodUid);
- if (windowIndex == C.INDEX_UNSET) {
- positionMs = 0;
- } else {
- positionMs =
- C.usToMs(
- currentTimeline.value.getWindow(windowIndex, window, /* setTag= */ false)
- .defaultPositionUs);
- }
- }
- playbackPositionMs.value = positionMs;
- lastPlaybackPositionChangeTimeMs = clock.elapsedRealtime();
- if (discontinuityReason != null) {
- notificationsBatch.add(
- new ListenerNotificationTask(
- listener -> listener.onPositionDiscontinuity(discontinuityReason)));
- }
- }
-
- // Internal methods.
-
- private void updateTimelineInternal(@TimelineChangeReason int changeReason) {
- currentTimeline.value =
- ExoCastTimeline.createTimelineFor(mediaItems, currentMediaItemInfoMap, currentShuffleOrder);
- removeStaleMediaItemInfo();
- notificationsBatch.add(
- new ListenerNotificationTask(
- listener ->
- listener.onTimelineChanged(
- currentTimeline.value, /* manifest= */ null, changeReason)));
- }
-
- private long projectPlaybackTimeElapsedMs() {
- return (long)
- ((clock.elapsedRealtime() - lastPlaybackPositionChangeTimeMs)
- * playbackParameters.value.speed);
- }
-
- private void flushNotifications() {
- boolean recursiveNotification = !ongoingNotificationsTasks.isEmpty();
- ongoingNotificationsTasks.addAll(notificationsBatch);
- notificationsBatch.clear();
- if (recursiveNotification) {
- // This will be handled once the current notification task is finished.
- return;
- }
- while (!ongoingNotificationsTasks.isEmpty()) {
- ongoingNotificationsTasks.peekFirst().execute();
- ongoingNotificationsTasks.removeFirst();
- }
- }
-
- /**
- * Updates the current media item information by including any extra entries received from the
- * receiver app.
- *
- * @param mediaItemsInformation A map of media item information received from the receiver app.
- */
- private void updateMediaItemsInfo(Map mediaItemsInformation) {
- for (Map.Entry entry : mediaItemsInformation.entrySet()) {
- MediaItemInfo currentInfoForEntry = currentMediaItemInfoMap.get(entry.getKey());
- boolean shouldPutEntry =
- currentInfoForEntry == null || !currentInfoForEntry.equals(entry.getValue());
- if (shouldPutEntry) {
- currentMediaItemInfoMap.put(entry.getKey(), entry.getValue());
- }
- }
- }
-
- /**
- * Removes stale media info entries. An entry is considered stale when the corresponding media
- * item is not present in the current media queue.
- */
- private void removeStaleMediaItemInfo() {
- for (Iterator iterator = currentMediaItemInfoMap.keySet().iterator();
- iterator.hasNext(); ) {
- UUID uuid = iterator.next();
- if (currentTimeline.value.getWindowIndexFromUuid(uuid) == C.INDEX_UNSET) {
- iterator.remove();
- }
- }
- }
-
- // Internal classes.
-
- private class SessionManagerStateListener implements CastSessionManager.StateListener {
-
- @Override
- public void onCastSessionAvailable() {
- if (sessionAvailabilityListener != null) {
- sessionAvailabilityListener.onCastSessionAvailable();
- }
- }
-
- @Override
- public void onCastSessionUnavailable() {
- if (sessionAvailabilityListener != null) {
- sessionAvailabilityListener.onCastSessionUnavailable();
- }
- }
-
- @Override
- public void onStateUpdateFromReceiverApp(ReceiverAppStateUpdate stateUpdate) {
- long sequence = stateUpdate.sequenceNumber;
-
- if (stateUpdate.errorMessage != null) {
- setPlaybackErrorInternal(stateUpdate.errorMessage);
- }
-
- if (sequence >= playbackState.sequence && stateUpdate.playbackState != null) {
- setPlaybackStateInternal(stateUpdate.playbackState);
- }
-
- if (sequence >= currentTimeline.sequence) {
- if (stateUpdate.items != null) {
- mediaItems.clear();
- mediaItems.addAll(stateUpdate.items);
- }
-
- currentShuffleOrder =
- stateUpdate.shuffleOrder != null
- ? new ShuffleOrder.DefaultShuffleOrder(
- Util.toArray(stateUpdate.shuffleOrder), clock.elapsedRealtime())
- : currentShuffleOrder;
- updateMediaItemsInfo(stateUpdate.mediaItemsInformation);
-
- if (playbackState.value != STATE_IDLE
- && !currentTimeline.value.representsMediaQueue(
- mediaItems, currentMediaItemInfoMap, currentShuffleOrder)) {
- updateTimelineInternal(TIMELINE_CHANGE_REASON_DYNAMIC);
- }
- }
-
- if (sequence >= currentPeriodUid.sequence
- && stateUpdate.currentPlayingItemUuid != null
- && stateUpdate.currentPlaybackPositionMs != null) {
- PeriodUid periodUid;
- if (stateUpdate.currentPlayingPeriodId == null) {
- int windowIndex =
- currentTimeline.value.getWindowIndexFromUuid(stateUpdate.currentPlayingItemUuid);
- periodUid =
- (PeriodUid)
- currentTimeline.value.getPeriodPosition(
- window,
- scratchPeriod,
- windowIndex,
- C.msToUs(stateUpdate.currentPlaybackPositionMs))
- .first;
- } else {
- periodUid =
- ExoCastTimeline.createPeriodUid(
- stateUpdate.currentPlayingItemUuid, stateUpdate.currentPlayingPeriodId);
- }
- setPlaybackPositionInternal(
- periodUid, stateUpdate.currentPlaybackPositionMs, stateUpdate.discontinuityReason);
- }
-
- if (sequence >= isLoading.sequence && stateUpdate.isLoading != null) {
- setIsLoadingInternal(stateUpdate.isLoading);
- }
-
- if (sequence >= playWhenReady.sequence && stateUpdate.playWhenReady != null) {
- setPlayWhenReadyInternal(stateUpdate.playWhenReady);
- }
-
- if (sequence >= shuffleModeEnabled.sequence && stateUpdate.shuffleModeEnabled != null) {
- setShuffleModeEnabledInternal(stateUpdate.shuffleModeEnabled);
- }
-
- if (sequence >= repeatMode.sequence && stateUpdate.repeatMode != null) {
- setRepeatModeInternal(stateUpdate.repeatMode);
- }
-
- if (sequence >= playbackParameters.sequence && stateUpdate.playbackParameters != null) {
- setPlaybackParametersInternal(stateUpdate.playbackParameters);
- }
-
- TrackSelectionParameters parameters = stateUpdate.trackSelectionParameters;
- if (sequence >= trackselectionParameters.sequence && parameters != null) {
- trackselectionParameters.value =
- trackselectionParameters
- .value
- .buildUpon()
- .setDisabledTextTrackSelectionFlags(parameters.disabledTextTrackSelectionFlags)
- .setPreferredAudioLanguage(parameters.preferredAudioLanguage)
- .setPreferredTextLanguage(parameters.preferredTextLanguage)
- .setSelectUndeterminedTextLanguage(parameters.selectUndeterminedTextLanguage)
- .build();
- }
-
- flushNotifications();
- }
- }
-
- private static final class StateHolder {
-
- public T value;
- public long sequence;
-
- public StateHolder(T initialValue) {
- value = initialValue;
- sequence = CastSessionManager.SEQUENCE_NUMBER_UNSET;
- }
- }
-
- private final class ListenerNotificationTask {
-
- private final Iterator listenersSnapshot;
- private final ListenerInvocation listenerInvocation;
-
- private ListenerNotificationTask(ListenerInvocation listenerInvocation) {
- this.listenersSnapshot = listeners.iterator();
- this.listenerInvocation = listenerInvocation;
- }
-
- public void execute() {
- while (listenersSnapshot.hasNext()) {
- listenersSnapshot.next().invoke(listenerInvocation);
- }
- }
- }
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastTimeline.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastTimeline.java
deleted file mode 100644
index 115536ac4c..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ExoCastTimeline.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.source.ShuffleOrder;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Util;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * A {@link Timeline} for Cast receiver app media queues.
- *
- * Each {@link MediaItem} in the timeline is exposed as a window. Unprepared media items are
- * exposed as an unset-duration {@link Window}, with a single unset-duration {@link Period}.
- */
-/* package */ final class ExoCastTimeline extends Timeline {
-
- /** Opaque object that uniquely identifies a period across timeline changes. */
- public interface PeriodUid {}
-
- /** A timeline for an empty media queue. */
- public static final ExoCastTimeline EMPTY =
- createTimelineFor(
- Collections.emptyList(), Collections.emptyMap(), new ShuffleOrder.DefaultShuffleOrder(0));
-
- /**
- * Creates {@link PeriodUid} from the given arguments.
- *
- * @param itemUuid The UUID that identifies the item.
- * @param periodId The id of the period for which the unique identifier is required.
- * @return An opaque unique identifier for a period.
- */
- public static PeriodUid createPeriodUid(UUID itemUuid, Object periodId) {
- return new PeriodUidImpl(itemUuid, periodId);
- }
-
- /**
- * Returns a new timeline representing the given media queue information.
- *
- * @param mediaItems The media items conforming the timeline.
- * @param mediaItemInfoMap Maps {@link MediaItem media items} in {@code mediaItems} to a {@link
- * MediaItemInfo} through their {@link MediaItem#uuid}. Media items may not have a {@link
- * MediaItemInfo} mapped to them.
- * @param shuffleOrder The {@link ShuffleOrder} of the timeline. {@link ShuffleOrder#getLength()}
- * must be equal to {@code mediaItems.size()}.
- * @return A new timeline representing the given media queue information.
- */
- public static ExoCastTimeline createTimelineFor(
- List mediaItems,
- Map mediaItemInfoMap,
- ShuffleOrder shuffleOrder) {
- Assertions.checkArgument(mediaItems.size() == shuffleOrder.getLength());
- int[] accumulativePeriodCount = new int[mediaItems.size()];
- int periodCount = 0;
- for (int i = 0; i < accumulativePeriodCount.length; i++) {
- periodCount += getInfoOrEmpty(mediaItemInfoMap, mediaItems.get(i).uuid).periods.size();
- accumulativePeriodCount[i] = periodCount;
- }
- HashMap uuidToIndex = new HashMap<>();
- for (int i = 0; i < mediaItems.size(); i++) {
- uuidToIndex.put(mediaItems.get(i).uuid, i);
- }
- return new ExoCastTimeline(
- Collections.unmodifiableList(new ArrayList<>(mediaItems)),
- Collections.unmodifiableMap(new HashMap<>(mediaItemInfoMap)),
- Collections.unmodifiableMap(new HashMap<>(uuidToIndex)),
- shuffleOrder,
- accumulativePeriodCount);
- }
-
- // Timeline backing information.
- private final List mediaItems;
- private final Map mediaItemInfoMap;
- private final ShuffleOrder shuffleOrder;
-
- // Precomputed for quick access.
- private final Map uuidToIndex;
- private final int[] accumulativePeriodCount;
-
- private ExoCastTimeline(
- List mediaItems,
- Map mediaItemInfoMap,
- Map uuidToIndex,
- ShuffleOrder shuffleOrder,
- int[] accumulativePeriodCount) {
- this.mediaItems = mediaItems;
- this.mediaItemInfoMap = mediaItemInfoMap;
- this.uuidToIndex = uuidToIndex;
- this.shuffleOrder = shuffleOrder;
- this.accumulativePeriodCount = accumulativePeriodCount;
- }
-
- /**
- * Returns whether the given media queue information would produce a timeline equivalent to this
- * one.
- *
- * @see ExoCastTimeline#createTimelineFor(List, Map, ShuffleOrder)
- */
- public boolean representsMediaQueue(
- List mediaItems,
- Map mediaItemInfoMap,
- ShuffleOrder shuffleOrder) {
- if (this.shuffleOrder.getLength() != shuffleOrder.getLength()) {
- return false;
- }
-
- int index = shuffleOrder.getFirstIndex();
- if (this.shuffleOrder.getFirstIndex() != index) {
- return false;
- }
- while (index != C.INDEX_UNSET) {
- int nextIndex = shuffleOrder.getNextIndex(index);
- if (nextIndex != this.shuffleOrder.getNextIndex(index)) {
- return false;
- }
- index = nextIndex;
- }
-
- if (mediaItems.size() != this.mediaItems.size()) {
- return false;
- }
- for (int i = 0; i < mediaItems.size(); i++) {
- UUID uuid = mediaItems.get(i).uuid;
- MediaItemInfo mediaItemInfo = getInfoOrEmpty(mediaItemInfoMap, uuid);
- if (!uuid.equals(this.mediaItems.get(i).uuid)
- || !mediaItemInfo.equals(getInfoOrEmpty(this.mediaItemInfoMap, uuid))) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns the index of the window that contains the period identified by the given {@code
- * periodUid} or {@link C#INDEX_UNSET} if this timeline does not contain any period with the given
- * {@code periodUid}.
- */
- public int getWindowIndexContainingPeriod(PeriodUid periodUid) {
- if (!(periodUid instanceof PeriodUidImpl)) {
- return C.INDEX_UNSET;
- }
- return getWindowIndexFromUuid(((PeriodUidImpl) periodUid).itemUuid);
- }
-
- /**
- * Returns the index of the window that represents the media item with the given {@code uuid} or
- * {@link C#INDEX_UNSET} if no item in this timeline has the given {@code uuid}.
- */
- public int getWindowIndexFromUuid(UUID uuid) {
- Integer index = uuidToIndex.get(uuid);
- return index != null ? index : C.INDEX_UNSET;
- }
-
- // Timeline implementation.
-
- @Override
- public int getWindowCount() {
- return mediaItems.size();
- }
-
- @Override
- public Window getWindow(
- int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
- MediaItem mediaItem = mediaItems.get(windowIndex);
- MediaItemInfo mediaItemInfo = getInfoOrEmpty(mediaItemInfoMap, mediaItem.uuid);
- return window.set(
- /* tag= */ setTag ? mediaItem.attachment : null,
- /* presentationStartTimeMs= */ C.TIME_UNSET,
- /* windowStartTimeMs= */ C.TIME_UNSET,
- /* isSeekable= */ mediaItemInfo.isSeekable,
- /* isDynamic= */ mediaItemInfo.isDynamic,
- /* defaultPositionUs= */ mediaItemInfo.defaultStartPositionUs,
- /* durationUs= */ mediaItemInfo.windowDurationUs,
- /* firstPeriodIndex= */ windowIndex == 0 ? 0 : accumulativePeriodCount[windowIndex - 1],
- /* lastPeriodIndex= */ accumulativePeriodCount[windowIndex] - 1,
- mediaItemInfo.positionInFirstPeriodUs);
- }
-
- @Override
- public int getPeriodCount() {
- return mediaItems.isEmpty() ? 0 : accumulativePeriodCount[accumulativePeriodCount.length - 1];
- }
-
- @Override
- public Period getPeriodByUid(Object periodUidObject, Period period) {
- return getPeriodInternal((PeriodUidImpl) periodUidObject, period, /* setIds= */ true);
- }
-
- @Override
- public Period getPeriod(int periodIndex, Period period, boolean setIds) {
- return getPeriodInternal((PeriodUidImpl) getUidOfPeriod(periodIndex), period, setIds);
- }
-
- @Override
- public int getIndexOfPeriod(Object uid) {
- if (!(uid instanceof PeriodUidImpl)) {
- return C.INDEX_UNSET;
- }
- PeriodUidImpl periodUid = (PeriodUidImpl) uid;
- UUID uuid = periodUid.itemUuid;
- Integer itemIndex = uuidToIndex.get(uuid);
- if (itemIndex == null) {
- return C.INDEX_UNSET;
- }
- int indexOfPeriodInItem =
- getInfoOrEmpty(mediaItemInfoMap, uuid).getIndexOfPeriod(periodUid.periodId);
- if (indexOfPeriodInItem == C.INDEX_UNSET) {
- return C.INDEX_UNSET;
- }
- return indexOfPeriodInItem + (itemIndex == 0 ? 0 : accumulativePeriodCount[itemIndex - 1]);
- }
-
- @Override
- public PeriodUid getUidOfPeriod(int periodIndex) {
- int mediaItemIndex = getMediaItemIndexForPeriodIndex(periodIndex);
- int periodIndexInMediaItem =
- periodIndex - (mediaItemIndex > 0 ? accumulativePeriodCount[mediaItemIndex - 1] : 0);
- UUID uuid = mediaItems.get(mediaItemIndex).uuid;
- MediaItemInfo mediaItemInfo = getInfoOrEmpty(mediaItemInfoMap, uuid);
- return new PeriodUidImpl(uuid, mediaItemInfo.periods.get(periodIndexInMediaItem).id);
- }
-
- @Override
- public int getFirstWindowIndex(boolean shuffleModeEnabled) {
- return shuffleModeEnabled ? shuffleOrder.getFirstIndex() : 0;
- }
-
- @Override
- public int getLastWindowIndex(boolean shuffleModeEnabled) {
- return shuffleModeEnabled ? shuffleOrder.getLastIndex() : mediaItems.size() - 1;
- }
-
- @Override
- public int getPreviousWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) {
- if (repeatMode == Player.REPEAT_MODE_ONE) {
- return windowIndex;
- } else if (windowIndex == getFirstWindowIndex(shuffleModeEnabled)) {
- return repeatMode == Player.REPEAT_MODE_OFF
- ? C.INDEX_UNSET
- : getLastWindowIndex(shuffleModeEnabled);
- } else if (shuffleModeEnabled) {
- return shuffleOrder.getPreviousIndex(windowIndex);
- } else {
- return windowIndex - 1;
- }
- }
-
- @Override
- public int getNextWindowIndex(int windowIndex, int repeatMode, boolean shuffleModeEnabled) {
- if (repeatMode == Player.REPEAT_MODE_ONE) {
- return windowIndex;
- } else if (windowIndex == getLastWindowIndex(shuffleModeEnabled)) {
- return repeatMode == Player.REPEAT_MODE_OFF
- ? C.INDEX_UNSET
- : getFirstWindowIndex(shuffleModeEnabled);
- } else if (shuffleModeEnabled) {
- return shuffleOrder.getNextIndex(windowIndex);
- } else {
- return windowIndex + 1;
- }
- }
-
- // Internal methods.
-
- private Period getPeriodInternal(PeriodUidImpl uid, Period period, boolean setIds) {
- UUID uuid = uid.itemUuid;
- int itemIndex = Assertions.checkNotNull(uuidToIndex.get(uuid));
- MediaItemInfo mediaItemInfo = getInfoOrEmpty(mediaItemInfoMap, uuid);
- MediaItemInfo.Period mediaInfoPeriod =
- mediaItemInfo.periods.get(mediaItemInfo.getIndexOfPeriod(uid.periodId));
- return period.set(
- setIds ? mediaInfoPeriod.id : null,
- setIds ? uid : null,
- /* windowIndex= */ itemIndex,
- mediaInfoPeriod.durationUs,
- mediaInfoPeriod.positionInWindowUs);
- }
-
- private int getMediaItemIndexForPeriodIndex(int periodIndex) {
- return Util.binarySearchCeil(
- accumulativePeriodCount, periodIndex, /* inclusive= */ false, /* stayInBounds= */ false);
- }
-
- private static MediaItemInfo getInfoOrEmpty(Map map, UUID uuid) {
- MediaItemInfo info = map.get(uuid);
- return info != null ? info : MediaItemInfo.EMPTY;
- }
-
- // Internal classes.
-
- private static final class PeriodUidImpl implements PeriodUid {
-
- public final UUID itemUuid;
- public final Object periodId;
-
- private PeriodUidImpl(UUID itemUuid, Object periodId) {
- this.itemUuid = itemUuid;
- this.periodId = periodId;
- }
-
- @Override
- public boolean equals(@Nullable Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
- PeriodUidImpl periodUid = (PeriodUidImpl) other;
- return itemUuid.equals(periodUid.itemUuid) && periodId.equals(periodUid.periodId);
- }
-
- @Override
- public int hashCode() {
- int result = itemUuid.hashCode();
- result = 31 * result + periodId.hashCode();
- return result;
- }
- }
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemInfo.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemInfo.java
deleted file mode 100644
index cb5eff4f37..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemInfo.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import androidx.annotation.Nullable;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.util.Util;
-import java.util.Collections;
-import java.util.List;
-
-// TODO (Internal b/119293631): Add ad playback state info.
-/**
- * Holds dynamic information for a {@link MediaItem}.
- *
- * Holds information related to preparation for a specific {@link MediaItem}. Unprepared items
- * are associated with an {@link #EMPTY} info object until prepared.
- */
-public final class MediaItemInfo {
-
- /** Placeholder information for media items that have not yet been prepared by the player. */
- public static final MediaItemInfo EMPTY =
- new MediaItemInfo(
- /* windowDurationUs= */ C.TIME_UNSET,
- /* defaultStartPositionUs= */ 0L,
- Collections.singletonList(
- new Period(
- /* id= */ new Object(),
- /* durationUs= */ C.TIME_UNSET,
- /* positionInWindowUs= */ 0L)),
- /* positionInFirstPeriodUs= */ 0L,
- /* isSeekable= */ false,
- /* isDynamic= */ true);
-
- /** Holds the information of one of the periods of a {@link MediaItem}. */
- public static final class Period {
-
- /**
- * The id of the period. Must be unique within the {@link MediaItem} but may match with periods
- * in other items.
- */
- public final Object id;
- /** The duration of the period in microseconds. */
- public final long durationUs;
- /** The position of this period in the window in microseconds. */
- public final long positionInWindowUs;
- // TODO: Add track information.
-
- public Period(Object id, long durationUs, long positionInWindowUs) {
- this.id = id;
- this.durationUs = durationUs;
- this.positionInWindowUs = positionInWindowUs;
- }
-
- @Override
- public boolean equals(@Nullable Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
-
- Period period = (Period) other;
- return durationUs == period.durationUs
- && positionInWindowUs == period.positionInWindowUs
- && id.equals(period.id);
- }
-
- @Override
- public int hashCode() {
- int result = id.hashCode();
- result = 31 * result + (int) (durationUs ^ (durationUs >>> 32));
- result = 31 * result + (int) (positionInWindowUs ^ (positionInWindowUs >>> 32));
- return result;
- }
- }
-
- /** The duration of the window in microseconds. */
- public final long windowDurationUs;
- /** The default start position relative to the start of the window, in microseconds. */
- public final long defaultStartPositionUs;
- /** The periods conforming the media item. */
- public final List periods;
- /** The position of the window in the first period in microseconds. */
- public final long positionInFirstPeriodUs;
- /** Whether it is possible to seek within the window. */
- public final boolean isSeekable;
- /** Whether the window may change when the timeline is updated. */
- public final boolean isDynamic;
-
- public MediaItemInfo(
- long windowDurationUs,
- long defaultStartPositionUs,
- List periods,
- long positionInFirstPeriodUs,
- boolean isSeekable,
- boolean isDynamic) {
- this.windowDurationUs = windowDurationUs;
- this.defaultStartPositionUs = defaultStartPositionUs;
- this.periods = Collections.unmodifiableList(periods);
- this.positionInFirstPeriodUs = positionInFirstPeriodUs;
- this.isSeekable = isSeekable;
- this.isDynamic = isDynamic;
- }
-
- /**
- * Returns the index of the period with {@link Period#id} equal to {@code periodId}, or {@link
- * C#INDEX_UNSET} if none of the periods has the given id.
- */
- public int getIndexOfPeriod(Object periodId) {
- for (int i = 0; i < periods.size(); i++) {
- if (Util.areEqual(periods.get(i).id, periodId)) {
- return i;
- }
- }
- return C.INDEX_UNSET;
- }
-
- @Override
- public boolean equals(@Nullable Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
-
- MediaItemInfo that = (MediaItemInfo) other;
- return windowDurationUs == that.windowDurationUs
- && defaultStartPositionUs == that.defaultStartPositionUs
- && positionInFirstPeriodUs == that.positionInFirstPeriodUs
- && isSeekable == that.isSeekable
- && isDynamic == that.isDynamic
- && periods.equals(that.periods);
- }
-
- @Override
- public int hashCode() {
- int result = (int) (windowDurationUs ^ (windowDurationUs >>> 32));
- result = 31 * result + (int) (defaultStartPositionUs ^ (defaultStartPositionUs >>> 32));
- result = 31 * result + periods.hashCode();
- result = 31 * result + (int) (positionInFirstPeriodUs ^ (positionInFirstPeriodUs >>> 32));
- result = 31 * result + (isSeekable ? 1 : 0);
- result = 31 * result + (isDynamic ? 1 : 0);
- return result;
- }
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemQueue.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemQueue.java
deleted file mode 100644
index 184e347e1c..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/MediaItemQueue.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-/** Represents a sequence of {@link MediaItem MediaItems}. */
-public interface MediaItemQueue {
-
- /**
- * Returns the item at the given index.
- *
- * @param index The index of the item to retrieve.
- * @return The item at the given index.
- * @throws IndexOutOfBoundsException If {@code index < 0 || index >= getSize()}.
- */
- MediaItem get(int index);
-
- /** Returns the number of items in this queue. */
- int getSize();
-
- /**
- * Appends the given sequence of items to the queue.
- *
- * @param items The sequence of items to append.
- */
- void add(MediaItem... items);
-
- /**
- * Adds the given sequence of items to the queue at the given position, so that the first of
- * {@code items} is placed at the given index.
- *
- * @param index The index at which {@code items} will be inserted.
- * @param items The sequence of items to append.
- * @throws IndexOutOfBoundsException If {@code index < 0 || index > getSize()}.
- */
- void add(int index, MediaItem... items);
-
- /**
- * Moves an existing item within the playlist.
- *
- * Calling this method is equivalent to removing the item at position {@code indexFrom} and
- * immediately inserting it at position {@code indexTo}. If the moved item is being played at the
- * moment of the invocation, playback will stick with the moved item.
- *
- * @param indexFrom The index of the item to move.
- * @param indexTo The index at which the item will be placed after this operation.
- * @throws IndexOutOfBoundsException If for either index, {@code index < 0 || index >= getSize()}.
- */
- void move(int indexFrom, int indexTo);
-
- /**
- * Removes an item from the queue.
- *
- * @param index The index of the item to remove from the queue.
- * @throws IndexOutOfBoundsException If {@code index < 0 || index >= getSize()}.
- */
- void remove(int index);
-
- /**
- * Removes a range of items from the queue.
- *
- *
Does nothing if an empty range ({@code from == exclusiveTo}) is passed.
- *
- * @param from The inclusive index at which the range to remove starts.
- * @param exclusiveTo The exclusive index at which the range to remove ends.
- * @throws IndexOutOfBoundsException If {@code from < 0 || exclusiveTo > getSize() || from >
- * exclusiveTo}.
- */
- void removeRange(int from, int exclusiveTo);
-
- /** Removes all items in the queue. */
- void clear();
-}
diff --git a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ReceiverAppStateUpdate.java b/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ReceiverAppStateUpdate.java
deleted file mode 100644
index c1b12428d4..0000000000
--- a/extensions/cast/src/main/java/com/google/android/exoplayer2/ext/cast/ReceiverAppStateUpdate.java
+++ /dev/null
@@ -1,633 +0,0 @@
-/*
- * Copyright (C) 2016 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.cast;
-
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_AD_INSERTION;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
-import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF;
-import static com.google.android.exoplayer2.Player.REPEAT_MODE_ONE;
-import static com.google.android.exoplayer2.Player.STATE_BUFFERING;
-import static com.google.android.exoplayer2.Player.STATE_ENDED;
-import static com.google.android.exoplayer2.Player.STATE_IDLE;
-import static com.google.android.exoplayer2.Player.STATE_READY;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DEFAULT_START_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DESCRIPTION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DISCONTINUITY_REASON;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DRM_SCHEMES;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DURATION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_END_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ERROR_MESSAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_IS_DYNAMIC;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_IS_LOADING;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_IS_SEEKABLE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_LICENSE_SERVER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MEDIA;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MEDIA_ITEMS_INFO;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MEDIA_QUEUE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MIME_TYPE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PERIODS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PERIOD_ID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PITCH;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAYBACK_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAYBACK_POSITION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAYBACK_STATE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAY_WHEN_READY;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_POSITION_IN_FIRST_PERIOD_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_POSITION_MS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_AUDIO_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_REPEAT_MODE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_REQUEST_HEADERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SEQUENCE_NUMBER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_MODE_ENABLED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_ORDER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SKIP_SILENCE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SPEED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_START_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_TITLE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_TRACK_SELECTION_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_URI;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_WINDOW_DURATION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_DISCONTINUITY_REASON_AD_INSERTION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_DISCONTINUITY_REASON_INTERNAL;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_DISCONTINUITY_REASON_PERIOD_TRANSITION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_DISCONTINUITY_REASON_SEEK;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_ALL;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_OFF;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_ONE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_STATE_BUFFERING;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_STATE_ENDED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_STATE_IDLE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_STATE_READY;
-
-import android.net.Uri;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
-import com.google.android.exoplayer2.util.Assertions;
-import com.google.android.exoplayer2.util.Util;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/** Holds a playback state update from the receiver app. */
-public final class ReceiverAppStateUpdate {
-
- /** Builder for {@link ReceiverAppStateUpdate}. */
- public static final class Builder {
-
- private final long sequenceNumber;
- private @MonotonicNonNull Boolean playWhenReady;
- private @MonotonicNonNull Integer playbackState;
- private @MonotonicNonNull List items;
- private @MonotonicNonNull Integer repeatMode;
- private @MonotonicNonNull Boolean shuffleModeEnabled;
- private @MonotonicNonNull Boolean isLoading;
- private @MonotonicNonNull PlaybackParameters playbackParameters;
- private @MonotonicNonNull TrackSelectionParameters trackSelectionParameters;
- private @MonotonicNonNull String errorMessage;
- private @MonotonicNonNull Integer discontinuityReason;
- private @MonotonicNonNull UUID currentPlayingItemUuid;
- private @MonotonicNonNull String currentPlayingPeriodId;
- private @MonotonicNonNull Long currentPlaybackPositionMs;
- private @MonotonicNonNull List shuffleOrder;
- private Map mediaItemsInformation;
-
- private Builder(long sequenceNumber) {
- this.sequenceNumber = sequenceNumber;
- mediaItemsInformation = Collections.emptyMap();
- }
-
- /** See {@link ReceiverAppStateUpdate#playWhenReady}. */
- public Builder setPlayWhenReady(Boolean playWhenReady) {
- this.playWhenReady = playWhenReady;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#playbackState}. */
- public Builder setPlaybackState(Integer playbackState) {
- this.playbackState = playbackState;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#items}. */
- public Builder setItems(List items) {
- this.items = Collections.unmodifiableList(items);
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#repeatMode}. */
- public Builder setRepeatMode(Integer repeatMode) {
- this.repeatMode = repeatMode;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#shuffleModeEnabled}. */
- public Builder setShuffleModeEnabled(Boolean shuffleModeEnabled) {
- this.shuffleModeEnabled = shuffleModeEnabled;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#isLoading}. */
- public Builder setIsLoading(Boolean isLoading) {
- this.isLoading = isLoading;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#playbackParameters}. */
- public Builder setPlaybackParameters(PlaybackParameters playbackParameters) {
- this.playbackParameters = playbackParameters;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#trackSelectionParameters} */
- public Builder setTrackSelectionParameters(TrackSelectionParameters trackSelectionParameters) {
- this.trackSelectionParameters = trackSelectionParameters;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#errorMessage}. */
- public Builder setErrorMessage(String errorMessage) {
- this.errorMessage = errorMessage;
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#discontinuityReason}. */
- public Builder setDiscontinuityReason(Integer discontinuityReason) {
- this.discontinuityReason = discontinuityReason;
- return this;
- }
-
- /**
- * See {@link ReceiverAppStateUpdate#currentPlayingItemUuid} and {@link
- * ReceiverAppStateUpdate#currentPlaybackPositionMs}.
- */
- public Builder setPlaybackPosition(
- UUID currentPlayingItemUuid,
- String currentPlayingPeriodId,
- Long currentPlaybackPositionMs) {
- this.currentPlayingItemUuid = currentPlayingItemUuid;
- this.currentPlayingPeriodId = currentPlayingPeriodId;
- this.currentPlaybackPositionMs = currentPlaybackPositionMs;
- return this;
- }
-
- /**
- * See {@link ReceiverAppStateUpdate#currentPlayingItemUuid} and {@link
- * ReceiverAppStateUpdate#currentPlaybackPositionMs}.
- */
- public Builder setMediaItemsInformation(Map mediaItemsInformation) {
- this.mediaItemsInformation = Collections.unmodifiableMap(mediaItemsInformation);
- return this;
- }
-
- /** See {@link ReceiverAppStateUpdate#shuffleOrder}. */
- public Builder setShuffleOrder(List shuffleOrder) {
- this.shuffleOrder = Collections.unmodifiableList(shuffleOrder);
- return this;
- }
-
- /**
- * Returns a new {@link ReceiverAppStateUpdate} instance with the current values in this
- * builder.
- */
- public ReceiverAppStateUpdate build() {
- return new ReceiverAppStateUpdate(
- sequenceNumber,
- playWhenReady,
- playbackState,
- items,
- repeatMode,
- shuffleModeEnabled,
- isLoading,
- playbackParameters,
- trackSelectionParameters,
- errorMessage,
- discontinuityReason,
- currentPlayingItemUuid,
- currentPlayingPeriodId,
- currentPlaybackPositionMs,
- mediaItemsInformation,
- shuffleOrder);
- }
- }
-
- /** Returns a {@link ReceiverAppStateUpdate} builder. */
- public static Builder builder(long sequenceNumber) {
- return new Builder(sequenceNumber);
- }
-
- /**
- * Creates an instance from parsing a state update received from the Receiver App.
- *
- * @param jsonMessage The state update encoded as a JSON string.
- * @return The parsed state update.
- * @throws JSONException If an error is encountered when parsing the {@code jsonMessage}.
- */
- public static ReceiverAppStateUpdate fromJsonMessage(String jsonMessage) throws JSONException {
- JSONObject stateAsJson = new JSONObject(jsonMessage);
- Builder builder = builder(stateAsJson.getLong(KEY_SEQUENCE_NUMBER));
-
- if (stateAsJson.has(KEY_PLAY_WHEN_READY)) {
- builder.setPlayWhenReady(stateAsJson.getBoolean(KEY_PLAY_WHEN_READY));
- }
-
- if (stateAsJson.has(KEY_PLAYBACK_STATE)) {
- builder.setPlaybackState(
- playbackStateStringToConstant(stateAsJson.getString(KEY_PLAYBACK_STATE)));
- }
-
- if (stateAsJson.has(KEY_MEDIA_QUEUE)) {
- builder.setItems(
- toMediaItemArrayList(Assertions.checkNotNull(stateAsJson.optJSONArray(KEY_MEDIA_QUEUE))));
- }
-
- if (stateAsJson.has(KEY_REPEAT_MODE)) {
- builder.setRepeatMode(stringToRepeatMode(stateAsJson.getString(KEY_REPEAT_MODE)));
- }
-
- if (stateAsJson.has(KEY_SHUFFLE_MODE_ENABLED)) {
- builder.setShuffleModeEnabled(stateAsJson.getBoolean(KEY_SHUFFLE_MODE_ENABLED));
- }
-
- if (stateAsJson.has(KEY_IS_LOADING)) {
- builder.setIsLoading(stateAsJson.getBoolean(KEY_IS_LOADING));
- }
-
- if (stateAsJson.has(KEY_PLAYBACK_PARAMETERS)) {
- builder.setPlaybackParameters(
- toPlaybackParameters(
- Assertions.checkNotNull(stateAsJson.optJSONObject(KEY_PLAYBACK_PARAMETERS))));
- }
-
- if (stateAsJson.has(KEY_TRACK_SELECTION_PARAMETERS)) {
- JSONObject trackSelectionParametersJson =
- stateAsJson.getJSONObject(KEY_TRACK_SELECTION_PARAMETERS);
- TrackSelectionParameters parameters =
- TrackSelectionParameters.DEFAULT
- .buildUpon()
- .setPreferredTextLanguage(
- trackSelectionParametersJson.getString(KEY_PREFERRED_TEXT_LANGUAGE))
- .setPreferredAudioLanguage(
- trackSelectionParametersJson.getString(KEY_PREFERRED_AUDIO_LANGUAGE))
- .setSelectUndeterminedTextLanguage(
- trackSelectionParametersJson.getBoolean(KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE))
- .setDisabledTextTrackSelectionFlags(
- jsonArrayToSelectionFlags(
- trackSelectionParametersJson.getJSONArray(
- KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS)))
- .build();
- builder.setTrackSelectionParameters(parameters);
- }
-
- if (stateAsJson.has(KEY_ERROR_MESSAGE)) {
- builder.setErrorMessage(stateAsJson.getString(KEY_ERROR_MESSAGE));
- }
-
- if (stateAsJson.has(KEY_PLAYBACK_POSITION)) {
- JSONObject playbackPosition = stateAsJson.getJSONObject(KEY_PLAYBACK_POSITION);
- String discontinuityReason = playbackPosition.optString(KEY_DISCONTINUITY_REASON);
- if (!discontinuityReason.isEmpty()) {
- builder.setDiscontinuityReason(stringToDiscontinuityReason(discontinuityReason));
- }
- UUID currentPlayingItemUuid = UUID.fromString(playbackPosition.getString(KEY_UUID));
- String currentPlayingPeriodId = playbackPosition.getString(KEY_PERIOD_ID);
- Long currentPlaybackPositionMs = playbackPosition.getLong(KEY_POSITION_MS);
- builder.setPlaybackPosition(
- currentPlayingItemUuid, currentPlayingPeriodId, currentPlaybackPositionMs);
- }
-
- if (stateAsJson.has(KEY_MEDIA_ITEMS_INFO)) {
- HashMap mediaItemInformation = new HashMap<>();
- JSONObject mediaItemsInfo = stateAsJson.getJSONObject(KEY_MEDIA_ITEMS_INFO);
- for (Iterator i = mediaItemsInfo.keys(); i.hasNext(); ) {
- String key = i.next();
- mediaItemInformation.put(
- UUID.fromString(key), jsonToMediaitemInfo(mediaItemsInfo.getJSONObject(key)));
- }
- builder.setMediaItemsInformation(mediaItemInformation);
- }
-
- if (stateAsJson.has(KEY_SHUFFLE_ORDER)) {
- ArrayList shuffleOrder = new ArrayList<>();
- JSONArray shuffleOrderJson = stateAsJson.getJSONArray(KEY_SHUFFLE_ORDER);
- for (int i = 0; i < shuffleOrderJson.length(); i++) {
- shuffleOrder.add(shuffleOrderJson.getInt(i));
- }
- builder.setShuffleOrder(shuffleOrder);
- }
-
- return builder.build();
- }
-
- /** The sequence number of the status update. */
- public final long sequenceNumber;
- /** Optional {@link Player#getPlayWhenReady playWhenReady} value. */
- @Nullable public final Boolean playWhenReady;
- /** Optional {@link Player#getPlaybackState() playbackState}. */
- @Nullable public final Integer playbackState;
- /** Optional list of media items. */
- @Nullable public final List items;
- /** Optional {@link Player#getRepeatMode() repeatMode}. */
- @Nullable public final Integer repeatMode;
- /** Optional {@link Player#getShuffleModeEnabled() shuffleMode}. */
- @Nullable public final Boolean shuffleModeEnabled;
- /** Optional {@link Player#isLoading() isLoading} value. */
- @Nullable public final Boolean isLoading;
- /** Optional {@link Player#getPlaybackParameters() playbackParameters}. */
- @Nullable public final PlaybackParameters playbackParameters;
- /** Optional {@link TrackSelectionParameters}. */
- @Nullable public final TrackSelectionParameters trackSelectionParameters;
- /** Optional error message string. */
- @Nullable public final String errorMessage;
- /**
- * Optional reason for a {@link Player.EventListener#onPositionDiscontinuity(int) discontinuity }
- * in the playback position.
- */
- @Nullable public final Integer discontinuityReason;
- /** Optional {@link UUID} of the {@link Player#getCurrentWindowIndex() currently played item}. */
- @Nullable public final UUID currentPlayingItemUuid;
- /** Optional id of the current {@link Player#getCurrentPeriodIndex() period being played}. */
- @Nullable public final String currentPlayingPeriodId;
- /** Optional {@link Player#getCurrentPosition() playbackPosition} in milliseconds. */
- @Nullable public final Long currentPlaybackPositionMs;
- /** Holds information about the {@link MediaItem media items} in the media queue. */
- public final Map mediaItemsInformation;
- /** Holds the indices of the media queue items in shuffle order. */
- @Nullable public final List shuffleOrder;
-
- /** Creates an instance with the given values. */
- private ReceiverAppStateUpdate(
- long sequenceNumber,
- @Nullable Boolean playWhenReady,
- @Nullable Integer playbackState,
- @Nullable List items,
- @Nullable Integer repeatMode,
- @Nullable Boolean shuffleModeEnabled,
- @Nullable Boolean isLoading,
- @Nullable PlaybackParameters playbackParameters,
- @Nullable TrackSelectionParameters trackSelectionParameters,
- @Nullable String errorMessage,
- @Nullable Integer discontinuityReason,
- @Nullable UUID currentPlayingItemUuid,
- @Nullable String currentPlayingPeriodId,
- @Nullable Long currentPlaybackPositionMs,
- Map mediaItemsInformation,
- @Nullable List shuffleOrder) {
- this.sequenceNumber = sequenceNumber;
- this.playWhenReady = playWhenReady;
- this.playbackState = playbackState;
- this.items = items;
- this.repeatMode = repeatMode;
- this.shuffleModeEnabled = shuffleModeEnabled;
- this.isLoading = isLoading;
- this.playbackParameters = playbackParameters;
- this.trackSelectionParameters = trackSelectionParameters;
- this.errorMessage = errorMessage;
- this.discontinuityReason = discontinuityReason;
- this.currentPlayingItemUuid = currentPlayingItemUuid;
- this.currentPlayingPeriodId = currentPlayingPeriodId;
- this.currentPlaybackPositionMs = currentPlaybackPositionMs;
- this.mediaItemsInformation = mediaItemsInformation;
- this.shuffleOrder = shuffleOrder;
- }
-
- @Override
- public boolean equals(@Nullable Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
- ReceiverAppStateUpdate that = (ReceiverAppStateUpdate) other;
-
- return sequenceNumber == that.sequenceNumber
- && Util.areEqual(playWhenReady, that.playWhenReady)
- && Util.areEqual(playbackState, that.playbackState)
- && Util.areEqual(items, that.items)
- && Util.areEqual(repeatMode, that.repeatMode)
- && Util.areEqual(shuffleModeEnabled, that.shuffleModeEnabled)
- && Util.areEqual(isLoading, that.isLoading)
- && Util.areEqual(playbackParameters, that.playbackParameters)
- && Util.areEqual(trackSelectionParameters, that.trackSelectionParameters)
- && Util.areEqual(errorMessage, that.errorMessage)
- && Util.areEqual(discontinuityReason, that.discontinuityReason)
- && Util.areEqual(currentPlayingItemUuid, that.currentPlayingItemUuid)
- && Util.areEqual(currentPlayingPeriodId, that.currentPlayingPeriodId)
- && Util.areEqual(currentPlaybackPositionMs, that.currentPlaybackPositionMs)
- && Util.areEqual(mediaItemsInformation, that.mediaItemsInformation)
- && Util.areEqual(shuffleOrder, that.shuffleOrder);
- }
-
- @Override
- public int hashCode() {
- int result = (int) (sequenceNumber ^ (sequenceNumber >>> 32));
- result = 31 * result + (playWhenReady != null ? playWhenReady.hashCode() : 0);
- result = 31 * result + (playbackState != null ? playbackState.hashCode() : 0);
- result = 31 * result + (items != null ? items.hashCode() : 0);
- result = 31 * result + (repeatMode != null ? repeatMode.hashCode() : 0);
- result = 31 * result + (shuffleModeEnabled != null ? shuffleModeEnabled.hashCode() : 0);
- result = 31 * result + (isLoading != null ? isLoading.hashCode() : 0);
- result = 31 * result + (playbackParameters != null ? playbackParameters.hashCode() : 0);
- result =
- 31 * result + (trackSelectionParameters != null ? trackSelectionParameters.hashCode() : 0);
- result = 31 * result + (errorMessage != null ? errorMessage.hashCode() : 0);
- result = 31 * result + (discontinuityReason != null ? discontinuityReason.hashCode() : 0);
- result = 31 * result + (currentPlayingItemUuid != null ? currentPlayingItemUuid.hashCode() : 0);
- result = 31 * result + (currentPlayingPeriodId != null ? currentPlayingPeriodId.hashCode() : 0);
- result =
- 31 * result
- + (currentPlaybackPositionMs != null ? currentPlaybackPositionMs.hashCode() : 0);
- result = 31 * result + mediaItemsInformation.hashCode();
- result = 31 * result + (shuffleOrder != null ? shuffleOrder.hashCode() : 0);
- return result;
- }
-
- // Internal methods.
-
- @VisibleForTesting
- /* package */ static List toMediaItemArrayList(JSONArray mediaItemsAsJson)
- throws JSONException {
- ArrayList mediaItems = new ArrayList<>();
- for (int i = 0; i < mediaItemsAsJson.length(); i++) {
- mediaItems.add(toMediaItem(mediaItemsAsJson.getJSONObject(i)));
- }
- return mediaItems;
- }
-
- private static MediaItem toMediaItem(JSONObject mediaItemAsJson) throws JSONException {
- MediaItem.Builder builder = new MediaItem.Builder();
- builder.setUuid(UUID.fromString(mediaItemAsJson.getString(KEY_UUID)));
- builder.setTitle(mediaItemAsJson.getString(KEY_TITLE));
- builder.setDescription(mediaItemAsJson.getString(KEY_DESCRIPTION));
- builder.setMedia(jsonToUriBundle(mediaItemAsJson.getJSONObject(KEY_MEDIA)));
- // TODO(Internal b/118431961): Add attachment management.
-
- builder.setDrmSchemes(jsonArrayToDrmSchemes(mediaItemAsJson.getJSONArray(KEY_DRM_SCHEMES)));
- if (mediaItemAsJson.has(KEY_START_POSITION_US)) {
- builder.setStartPositionUs(mediaItemAsJson.getLong(KEY_START_POSITION_US));
- }
- if (mediaItemAsJson.has(KEY_END_POSITION_US)) {
- builder.setEndPositionUs(mediaItemAsJson.getLong(KEY_END_POSITION_US));
- }
- builder.setMimeType(mediaItemAsJson.getString(KEY_MIME_TYPE));
- return builder.build();
- }
-
- private static PlaybackParameters toPlaybackParameters(JSONObject parameters)
- throws JSONException {
- float speed = (float) parameters.getDouble(KEY_SPEED);
- float pitch = (float) parameters.getDouble(KEY_PITCH);
- boolean skipSilence = parameters.getBoolean(KEY_SKIP_SILENCE);
- return new PlaybackParameters(speed, pitch, skipSilence);
- }
-
- private static int playbackStateStringToConstant(String string) {
- switch (string) {
- case STR_STATE_IDLE:
- return STATE_IDLE;
- case STR_STATE_BUFFERING:
- return STATE_BUFFERING;
- case STR_STATE_READY:
- return STATE_READY;
- case STR_STATE_ENDED:
- return STATE_ENDED;
- default:
- throw new AssertionError("Unexpected state string: " + string);
- }
- }
-
- private static Integer stringToRepeatMode(String repeatModeStr) {
- switch (repeatModeStr) {
- case STR_REPEAT_MODE_OFF:
- return REPEAT_MODE_OFF;
- case STR_REPEAT_MODE_ONE:
- return REPEAT_MODE_ONE;
- case STR_REPEAT_MODE_ALL:
- return REPEAT_MODE_ALL;
- default:
- throw new AssertionError("Illegal repeat mode: " + repeatModeStr);
- }
- }
-
- private static Integer stringToDiscontinuityReason(String discontinuityReasonStr) {
- switch (discontinuityReasonStr) {
- case STR_DISCONTINUITY_REASON_PERIOD_TRANSITION:
- return DISCONTINUITY_REASON_PERIOD_TRANSITION;
- case STR_DISCONTINUITY_REASON_SEEK:
- return DISCONTINUITY_REASON_SEEK;
- case STR_DISCONTINUITY_REASON_SEEK_ADJUSTMENT:
- return DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
- case STR_DISCONTINUITY_REASON_AD_INSERTION:
- return DISCONTINUITY_REASON_AD_INSERTION;
- case STR_DISCONTINUITY_REASON_INTERNAL:
- return DISCONTINUITY_REASON_INTERNAL;
- default:
- throw new AssertionError("Illegal discontinuity reason: " + discontinuityReasonStr);
- }
- }
-
- @C.SelectionFlags
- private static int jsonArrayToSelectionFlags(JSONArray array) throws JSONException {
- int result = 0;
- for (int i = 0; i < array.length(); i++) {
- switch (array.getString(i)) {
- case ExoCastConstants.STR_SELECTION_FLAG_AUTOSELECT:
- result |= C.SELECTION_FLAG_AUTOSELECT;
- break;
- case ExoCastConstants.STR_SELECTION_FLAG_FORCED:
- result |= C.SELECTION_FLAG_FORCED;
- break;
- case ExoCastConstants.STR_SELECTION_FLAG_DEFAULT:
- result |= C.SELECTION_FLAG_DEFAULT;
- break;
- default:
- // Do nothing.
- break;
- }
- }
- return result;
- }
-
- private static List jsonArrayToDrmSchemes(JSONArray drmSchemesAsJson)
- throws JSONException {
- ArrayList drmSchemes = new ArrayList<>();
- for (int i = 0; i < drmSchemesAsJson.length(); i++) {
- JSONObject drmSchemeAsJson = drmSchemesAsJson.getJSONObject(i);
- MediaItem.UriBundle uriBundle =
- drmSchemeAsJson.has(KEY_LICENSE_SERVER)
- ? jsonToUriBundle(drmSchemeAsJson.getJSONObject(KEY_LICENSE_SERVER))
- : null;
- drmSchemes.add(
- new MediaItem.DrmScheme(UUID.fromString(drmSchemeAsJson.getString(KEY_UUID)), uriBundle));
- }
- return Collections.unmodifiableList(drmSchemes);
- }
-
- private static MediaItem.UriBundle jsonToUriBundle(JSONObject json) throws JSONException {
- Uri uri = Uri.parse(json.getString(KEY_URI));
- JSONObject requestHeadersAsJson = json.getJSONObject(KEY_REQUEST_HEADERS);
- HashMap requestHeaders = new HashMap<>();
- for (Iterator i = requestHeadersAsJson.keys(); i.hasNext(); ) {
- String key = i.next();
- requestHeaders.put(key, requestHeadersAsJson.getString(key));
- }
- return new MediaItem.UriBundle(uri, requestHeaders);
- }
-
- private static MediaItemInfo jsonToMediaitemInfo(JSONObject json) throws JSONException {
- long durationUs = json.getLong(KEY_WINDOW_DURATION_US);
- long defaultPositionUs = json.optLong(KEY_DEFAULT_START_POSITION_US, /* fallback= */ 0L);
- JSONArray periodsJson = json.getJSONArray(KEY_PERIODS);
- ArrayList periods = new ArrayList<>();
- long positionInFirstPeriodUs = json.getLong(KEY_POSITION_IN_FIRST_PERIOD_US);
-
- long windowPositionUs = -positionInFirstPeriodUs;
- for (int i = 0; i < periodsJson.length(); i++) {
- JSONObject periodJson = periodsJson.getJSONObject(i);
- long periodDurationUs = periodJson.optLong(KEY_DURATION_US, C.TIME_UNSET);
- periods.add(
- new MediaItemInfo.Period(
- periodJson.getString(KEY_ID), periodDurationUs, windowPositionUs));
- windowPositionUs += periodDurationUs;
- }
- boolean isDynamic = json.getBoolean(KEY_IS_DYNAMIC);
- boolean isSeekable = json.getBoolean(KEY_IS_SEEKABLE);
- return new MediaItemInfo(
- durationUs, defaultPositionUs, periods, positionInFirstPeriodUs, isSeekable, isDynamic);
- }
-}
diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastMessageTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastMessageTest.java
deleted file mode 100644
index b900a78937..0000000000
--- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastMessageTest.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ARGS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DESCRIPTION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DRM_SCHEMES;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_END_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_INDEX;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ITEMS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_LICENSE_SERVER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MEDIA;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_METHOD;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MIME_TYPE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PITCH;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAY_WHEN_READY;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_POSITION_MS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_AUDIO_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_REPEAT_MODE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_REQUEST_HEADERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SEQUENCE_NUMBER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_MODE_ENABLED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_ORDER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SKIP_SILENCE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SPEED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_START_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_TITLE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_URI;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUIDS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_ADD_ITEMS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_MOVE_ITEM;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_REMOVE_ITEMS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SEEK_TO;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_PLAYBACK_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_PLAY_WHEN_READY;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_REPEAT_MODE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_SHUFFLE_MODE_ENABLED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.METHOD_SET_TRACK_SELECTION_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_ALL;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_OFF;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_ONE;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.net.Uri;
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.ext.cast.MediaItem.DrmScheme;
-import com.google.android.exoplayer2.ext.cast.MediaItem.UriBundle;
-import com.google.android.exoplayer2.source.ShuffleOrder;
-import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
-import com.google.android.exoplayer2.util.MimeTypes;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link ExoCastMessage}. */
-@RunWith(AndroidJUnit4.class)
-public class ExoCastMessageTest {
-
- @Test
- public void addItems_withUnsetIndex_doesNotAddIndexToJson() throws JSONException {
- MediaItem sampleItem = new MediaItem.Builder().build();
- ExoCastMessage message =
- new ExoCastMessage.AddItems(
- C.INDEX_UNSET,
- Collections.singletonList(sampleItem),
- new ShuffleOrder.UnshuffledShuffleOrder(1));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
- JSONArray items = arguments.getJSONArray(KEY_ITEMS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_ADD_ITEMS);
- assertThat(arguments.has(KEY_INDEX)).isFalse();
- assertThat(items.length()).isEqualTo(1);
- }
-
- @Test
- public void addItems_withMultipleItems_producesExpectedJsonList() throws JSONException {
- MediaItem sampleItem1 = new MediaItem.Builder().build();
- MediaItem sampleItem2 = new MediaItem.Builder().build();
- ExoCastMessage message =
- new ExoCastMessage.AddItems(
- 1, Arrays.asList(sampleItem2, sampleItem1), new ShuffleOrder.UnshuffledShuffleOrder(2));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 1));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
- JSONArray items = arguments.getJSONArray(KEY_ITEMS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(1);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_ADD_ITEMS);
- assertThat(arguments.getInt(KEY_INDEX)).isEqualTo(1);
- assertThat(items.length()).isEqualTo(2);
- }
-
- @Test
- public void addItems_withoutItemOptionalFields_doesNotAddFieldsToJson() throws JSONException {
- MediaItem itemWithoutOptionalFields =
- new MediaItem.Builder()
- .setTitle("title")
- .setMimeType(MimeTypes.AUDIO_MP4)
- .setDescription("desc")
- .setDrmSchemes(Collections.singletonList(new DrmScheme(C.WIDEVINE_UUID, null)))
- .setMedia("www.google.com")
- .build();
- ExoCastMessage message =
- new ExoCastMessage.AddItems(
- C.INDEX_UNSET,
- Collections.singletonList(itemWithoutOptionalFields),
- new ShuffleOrder.UnshuffledShuffleOrder(1));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
- JSONArray items = arguments.getJSONArray(KEY_ITEMS);
-
- assertJsonEqualsMediaItem(items.getJSONObject(/* index= */ 0), itemWithoutOptionalFields);
- }
-
- @Test
- public void addItems_withAllItemFields_addsFieldsToJson() throws JSONException {
- HashMap headersMedia = new HashMap<>();
- headersMedia.put("header1", "value1");
- headersMedia.put("header2", "value2");
- UriBundle media = new UriBundle(Uri.parse("www.google.com"), headersMedia);
-
- HashMap headersWidevine = new HashMap<>();
- headersWidevine.put("widevine", "value");
- UriBundle widevingUriBundle = new UriBundle(Uri.parse("www.widevine.com"), headersWidevine);
-
- HashMap headersPlayready = new HashMap<>();
- headersPlayready.put("playready", "value");
- UriBundle playreadyUriBundle = new UriBundle(Uri.parse("www.playready.com"), headersPlayready);
-
- DrmScheme[] drmSchemes =
- new DrmScheme[] {
- new DrmScheme(C.WIDEVINE_UUID, widevingUriBundle),
- new DrmScheme(C.PLAYREADY_UUID, playreadyUriBundle)
- };
- MediaItem itemWithAllFields =
- new MediaItem.Builder()
- .setTitle("title")
- .setMimeType(MimeTypes.VIDEO_MP4)
- .setDescription("desc")
- .setStartPositionUs(3)
- .setEndPositionUs(10)
- .setDrmSchemes(Arrays.asList(drmSchemes))
- .setMedia(media)
- .build();
- ExoCastMessage message =
- new ExoCastMessage.AddItems(
- C.INDEX_UNSET,
- Collections.singletonList(itemWithAllFields),
- new ShuffleOrder.UnshuffledShuffleOrder(1));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
- JSONArray items = arguments.getJSONArray(KEY_ITEMS);
-
- assertJsonEqualsMediaItem(items.getJSONObject(/* index= */ 0), itemWithAllFields);
- }
-
- @Test
- public void addItems_withShuffleOrder_producesExpectedJson() throws JSONException {
- MediaItem.Builder builder = new MediaItem.Builder();
- MediaItem sampleItem1 = builder.build();
- MediaItem sampleItem2 = builder.build();
- MediaItem sampleItem3 = builder.build();
- MediaItem sampleItem4 = builder.build();
-
- ExoCastMessage message =
- new ExoCastMessage.AddItems(
- C.INDEX_UNSET,
- Arrays.asList(sampleItem1, sampleItem2, sampleItem3, sampleItem4),
- new ShuffleOrder.DefaultShuffleOrder(new int[] {2, 1, 3, 0}, /* randomSeed= */ 0));
- JSONObject arguments =
- new JSONObject(message.toJsonString(/* sequenceNumber= */ 0)).getJSONObject(KEY_ARGS);
- JSONArray shuffledIndices = arguments.getJSONArray(KEY_SHUFFLE_ORDER);
- assertThat(shuffledIndices.getInt(0)).isEqualTo(2);
- assertThat(shuffledIndices.getInt(1)).isEqualTo(1);
- assertThat(shuffledIndices.getInt(2)).isEqualTo(3);
- assertThat(shuffledIndices.getInt(3)).isEqualTo(0);
- }
-
- @Test
- public void moveItem_producesExpectedJson() throws JSONException {
- ExoCastMessage message =
- new ExoCastMessage.MoveItem(
- new UUID(0, 1),
- /* index= */ 3,
- new ShuffleOrder.DefaultShuffleOrder(new int[] {2, 1, 3, 0}, /* randomSeed= */ 0));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 1));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(1);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_MOVE_ITEM);
- assertThat(arguments.getString(KEY_UUID)).isEqualTo(new UUID(0, 1).toString());
- assertThat(arguments.getInt(KEY_INDEX)).isEqualTo(3);
- JSONArray shuffledIndices = arguments.getJSONArray(KEY_SHUFFLE_ORDER);
- assertThat(shuffledIndices.getInt(0)).isEqualTo(2);
- assertThat(shuffledIndices.getInt(1)).isEqualTo(1);
- assertThat(shuffledIndices.getInt(2)).isEqualTo(3);
- assertThat(shuffledIndices.getInt(3)).isEqualTo(0);
- }
-
- @Test
- public void removeItems_withSingleItem_producesExpectedJson() throws JSONException {
- ExoCastMessage message =
- new ExoCastMessage.RemoveItems(Collections.singletonList(new UUID(0, 1)));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONArray uuids = messageAsJson.getJSONObject(KEY_ARGS).getJSONArray(KEY_UUIDS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_REMOVE_ITEMS);
- assertThat(uuids.length()).isEqualTo(1);
- assertThat(uuids.getString(0)).isEqualTo(new UUID(0, 1).toString());
- }
-
- @Test
- public void removeItems_withMultipleItems_producesExpectedJson() throws JSONException {
- ExoCastMessage message =
- new ExoCastMessage.RemoveItems(
- Arrays.asList(new UUID(0, 1), new UUID(0, 2), new UUID(0, 3)));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONArray uuids = messageAsJson.getJSONObject(KEY_ARGS).getJSONArray(KEY_UUIDS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_REMOVE_ITEMS);
- assertThat(uuids.length()).isEqualTo(3);
- assertThat(uuids.getString(0)).isEqualTo(new UUID(0, 1).toString());
- assertThat(uuids.getString(1)).isEqualTo(new UUID(0, 2).toString());
- assertThat(uuids.getString(2)).isEqualTo(new UUID(0, 3).toString());
- }
-
- @Test
- public void setPlayWhenReady_producesExpectedJson() throws JSONException {
- ExoCastMessage message = new ExoCastMessage.SetPlayWhenReady(true);
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SET_PLAY_WHEN_READY);
- assertThat(messageAsJson.getJSONObject(KEY_ARGS).getBoolean(KEY_PLAY_WHEN_READY)).isTrue();
- }
-
- @Test
- public void setRepeatMode_withRepeatModeOff_producesExpectedJson() throws JSONException {
- ExoCastMessage message = new ExoCastMessage.SetRepeatMode(Player.REPEAT_MODE_OFF);
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SET_REPEAT_MODE);
- assertThat(messageAsJson.getJSONObject(KEY_ARGS).getString(KEY_REPEAT_MODE))
- .isEqualTo(STR_REPEAT_MODE_OFF);
- }
-
- @Test
- public void setRepeatMode_withRepeatModeOne_producesExpectedJson() throws JSONException {
- ExoCastMessage message = new ExoCastMessage.SetRepeatMode(Player.REPEAT_MODE_ONE);
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SET_REPEAT_MODE);
- assertThat(messageAsJson.getJSONObject(KEY_ARGS).getString(KEY_REPEAT_MODE))
- .isEqualTo(STR_REPEAT_MODE_ONE);
- }
-
- @Test
- public void setRepeatMode_withRepeatModeAll_producesExpectedJson() throws JSONException {
- ExoCastMessage message = new ExoCastMessage.SetRepeatMode(Player.REPEAT_MODE_ALL);
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SET_REPEAT_MODE);
- assertThat(messageAsJson.getJSONObject(KEY_ARGS).getString(KEY_REPEAT_MODE))
- .isEqualTo(STR_REPEAT_MODE_ALL);
- }
-
- @Test
- public void setShuffleModeEnabled_producesExpectedJson() throws JSONException {
- ExoCastMessage message =
- new ExoCastMessage.SetShuffleModeEnabled(/* shuffleModeEnabled= */ false);
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SET_SHUFFLE_MODE_ENABLED);
- assertThat(messageAsJson.getJSONObject(KEY_ARGS).getBoolean(KEY_SHUFFLE_MODE_ENABLED))
- .isFalse();
- }
-
- @Test
- public void seekTo_withPositionInItem_addsPositionField() throws JSONException {
- ExoCastMessage message = new ExoCastMessage.SeekTo(new UUID(0, 1), /* positionMs= */ 10);
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SEEK_TO);
- assertThat(arguments.getString(KEY_UUID)).isEqualTo(new UUID(0, 1).toString());
- assertThat(arguments.getLong(KEY_POSITION_MS)).isEqualTo(10);
- }
-
- @Test
- public void seekTo_withUnsetPosition_doesNotAddPositionField() throws JSONException {
- ExoCastMessage message =
- new ExoCastMessage.SeekTo(new UUID(0, 1), /* positionMs= */ C.TIME_UNSET);
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SEEK_TO);
- assertThat(arguments.getString(KEY_UUID)).isEqualTo(new UUID(0, 1).toString());
- assertThat(arguments.has(KEY_POSITION_MS)).isFalse();
- }
-
- @Test
- public void setPlaybackParameters_producesExpectedJson() throws JSONException {
- ExoCastMessage message =
- new ExoCastMessage.SetPlaybackParameters(
- new PlaybackParameters(/* speed= */ 0.5f, /* pitch= */ 2, /* skipSilence= */ false));
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD)).isEqualTo(METHOD_SET_PLAYBACK_PARAMETERS);
- assertThat(arguments.getDouble(KEY_SPEED)).isEqualTo(0.5);
- assertThat(arguments.getDouble(KEY_PITCH)).isEqualTo(2.0);
- assertThat(arguments.getBoolean(KEY_SKIP_SILENCE)).isFalse();
- }
-
- @Test
- public void setSelectionParameters_producesExpectedJson() throws JSONException {
- ExoCastMessage message =
- new ExoCastMessage.SetTrackSelectionParameters(
- TrackSelectionParameters.DEFAULT
- .buildUpon()
- .setDisabledTextTrackSelectionFlags(
- C.SELECTION_FLAG_AUTOSELECT | C.SELECTION_FLAG_DEFAULT)
- .setSelectUndeterminedTextLanguage(true)
- .setPreferredAudioLanguage("esp")
- .setPreferredTextLanguage("deu")
- .build());
- JSONObject messageAsJson = new JSONObject(message.toJsonString(/* sequenceNumber= */ 0));
- JSONObject arguments = messageAsJson.getJSONObject(KEY_ARGS);
-
- assertThat(messageAsJson.getLong(KEY_SEQUENCE_NUMBER)).isEqualTo(0);
- assertThat(messageAsJson.getString(KEY_METHOD))
- .isEqualTo(METHOD_SET_TRACK_SELECTION_PARAMETERS);
- assertThat(arguments.getBoolean(KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE)).isTrue();
- assertThat(arguments.getString(KEY_PREFERRED_AUDIO_LANGUAGE)).isEqualTo("esp");
- assertThat(arguments.getString(KEY_PREFERRED_TEXT_LANGUAGE)).isEqualTo("deu");
- ArrayList selectionFlagStrings = new ArrayList<>();
- JSONArray selectionFlagsJson = arguments.getJSONArray(KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS);
- for (int i = 0; i < selectionFlagsJson.length(); i++) {
- selectionFlagStrings.add(selectionFlagsJson.getString(i));
- }
- assertThat(selectionFlagStrings).contains(ExoCastConstants.STR_SELECTION_FLAG_AUTOSELECT);
- assertThat(selectionFlagStrings).doesNotContain(ExoCastConstants.STR_SELECTION_FLAG_FORCED);
- assertThat(selectionFlagStrings).contains(ExoCastConstants.STR_SELECTION_FLAG_DEFAULT);
- }
-
- private static void assertJsonEqualsMediaItem(JSONObject itemAsJson, MediaItem mediaItem)
- throws JSONException {
- assertThat(itemAsJson.getString(KEY_UUID)).isEqualTo(mediaItem.uuid.toString());
- assertThat(itemAsJson.getString(KEY_TITLE)).isEqualTo(mediaItem.title);
- assertThat(itemAsJson.getString(KEY_MIME_TYPE)).isEqualTo(mediaItem.mimeType);
- assertThat(itemAsJson.getString(KEY_DESCRIPTION)).isEqualTo(mediaItem.description);
- assertJsonMatchesTimestamp(itemAsJson, KEY_START_POSITION_US, mediaItem.startPositionUs);
- assertJsonMatchesTimestamp(itemAsJson, KEY_END_POSITION_US, mediaItem.endPositionUs);
- assertJsonMatchesUriBundle(itemAsJson, KEY_MEDIA, mediaItem.media);
-
- List drmSchemes = mediaItem.drmSchemes;
- int drmSchemesLength = drmSchemes.size();
- JSONArray drmSchemesAsJson = itemAsJson.getJSONArray(KEY_DRM_SCHEMES);
-
- assertThat(drmSchemesAsJson.length()).isEqualTo(drmSchemesLength);
- for (int i = 0; i < drmSchemesLength; i++) {
- DrmScheme drmScheme = drmSchemes.get(i);
- JSONObject drmSchemeAsJson = drmSchemesAsJson.getJSONObject(i);
-
- assertThat(drmSchemeAsJson.getString(KEY_UUID)).isEqualTo(drmScheme.uuid.toString());
- assertJsonMatchesUriBundle(drmSchemeAsJson, KEY_LICENSE_SERVER, drmScheme.licenseServer);
- }
- }
-
- private static void assertJsonMatchesUriBundle(
- JSONObject jsonObject, String key, @Nullable UriBundle uriBundle) throws JSONException {
- if (uriBundle == null) {
- assertThat(jsonObject.has(key)).isFalse();
- return;
- }
- JSONObject uriBundleAsJson = jsonObject.getJSONObject(key);
- assertThat(uriBundleAsJson.getString(KEY_URI)).isEqualTo(uriBundle.uri.toString());
- Map requestHeaders = uriBundle.requestHeaders;
- JSONObject requestHeadersAsJson = uriBundleAsJson.getJSONObject(KEY_REQUEST_HEADERS);
-
- assertThat(requestHeadersAsJson.length()).isEqualTo(requestHeaders.size());
- for (String headerKey : requestHeaders.keySet()) {
- assertThat(requestHeadersAsJson.getString(headerKey))
- .isEqualTo(requestHeaders.get(headerKey));
- }
- }
-
- private static void assertJsonMatchesTimestamp(JSONObject object, String key, long timestamp)
- throws JSONException {
- if (timestamp == C.TIME_UNSET) {
- assertThat(object.has(key)).isFalse();
- } else {
- assertThat(object.getLong(key)).isEqualTo(timestamp);
- }
- }
-}
diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastPlayerTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastPlayerTest.java
deleted file mode 100644
index 58f78b090a..0000000000
--- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastPlayerTest.java
+++ /dev/null
@@ -1,1018 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ARGS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_INDEX;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ITEMS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUIDS;
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.testutil.FakeClock;
-import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
-import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.UUID;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-
-/** Unit test for {@link ExoCastPlayer}. */
-@RunWith(AndroidJUnit4.class)
-public class ExoCastPlayerTest {
-
- private static final long MOCK_SEQUENCE_NUMBER = 1;
- private ExoCastPlayer player;
- private MediaItem.Builder itemBuilder;
- private CastSessionManager.StateListener receiverAppStateListener;
- private FakeClock clock;
- @Mock private CastSessionManager sessionManager;
- @Mock private SessionAvailabilityListener sessionAvailabilityListener;
- @Mock private Player.EventListener playerEventListener;
-
- @Before
- public void setUp() {
- initMocks(this);
- clock = new FakeClock(/* initialTimeMs= */ 0);
- player =
- new ExoCastPlayer(
- listener -> {
- receiverAppStateListener = listener;
- return sessionManager;
- },
- clock);
- player.addListener(playerEventListener);
- itemBuilder = new MediaItem.Builder();
- }
-
- @Test
- public void exoCastPlayer_startsAndStopsSessionManager() {
- // The session manager should have been started when setting up, with the creation of
- // ExoCastPlayer.
- verify(sessionManager).start();
- verifyNoMoreInteractions(sessionManager);
- player.release();
- verify(sessionManager).stopTrackingSession();
- verifyNoMoreInteractions(sessionManager);
- }
-
- @Test
- public void exoCastPlayer_propagatesSessionStatus() {
- player.setSessionAvailabilityListener(sessionAvailabilityListener);
- verify(sessionAvailabilityListener, never()).onCastSessionAvailable();
- receiverAppStateListener.onCastSessionAvailable();
- verify(sessionAvailabilityListener).onCastSessionAvailable();
- verifyNoMoreInteractions(sessionAvailabilityListener);
- receiverAppStateListener.onCastSessionUnavailable();
- verify(sessionAvailabilityListener).onCastSessionUnavailable();
- verifyNoMoreInteractions(sessionAvailabilityListener);
- }
-
- @Test
- public void addItemsToQueue_producesExpectedMessages() throws JSONException {
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- MediaItem item3 = itemBuilder.setUuid(toUuid(3)).build();
- MediaItem item4 = itemBuilder.setUuid(toUuid(4)).build();
- MediaItem item5 = itemBuilder.setUuid(toUuid(5)).build();
-
- player.addItemsToQueue(item1, item2);
- assertMediaItemQueue(item1, item2);
-
- player.addItemsToQueue(1, item3, item4);
- assertMediaItemQueue(item1, item3, item4, item2);
-
- player.addItemsToQueue(item5);
- assertMediaItemQueue(item1, item3, item4, item2, item5);
-
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(ExoCastMessage.class);
- verify(sessionManager, times(3)).send(messageCaptor.capture());
- assertMessageAddsItems(
- /* message= */ messageCaptor.getAllValues().get(0),
- /* index= */ C.INDEX_UNSET,
- Arrays.asList(item1, item2));
- assertMessageAddsItems(
- /* message= */ messageCaptor.getAllValues().get(1),
- /* index= */ 1,
- Arrays.asList(item3, item4));
- assertMessageAddsItems(
- /* message= */ messageCaptor.getAllValues().get(2),
- /* index= */ C.INDEX_UNSET,
- Collections.singletonList(item5));
- }
-
- @Test
- public void addItemsToQueue_masksRemoteUpdates() {
- player.prepare();
- when(sessionManager.send(any(ExoCastMessage.class))).thenReturn(3L);
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- MediaItem item3 = itemBuilder.setUuid(toUuid(3)).build();
- MediaItem item4 = itemBuilder.setUuid(toUuid(4)).build();
-
- player.addItemsToQueue(item1, item2);
- assertMediaItemQueue(item1, item2);
-
- // Should be ignored due to a lower sequence number.
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 2)
- .setItems(Arrays.asList(item3, item4))
- .build());
-
- // Should override the current state.
- assertMediaItemQueue(item1, item2);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 3)
- .setItems(Arrays.asList(item3, item4))
- .build());
-
- assertMediaItemQueue(item3, item4);
- }
-
- @Test
- public void addItemsToQueue_masksWindowIndexAsExpected() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(2);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(2);
- player.addItemsToQueue(/* optionalIndex= */ 0, itemBuilder.build());
- assertThat(player.getCurrentWindowIndex()).isEqualTo(3);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(3);
-
- player.addItemsToQueue(itemBuilder.build());
- assertThat(player.getCurrentWindowIndex()).isEqualTo(3);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(3);
- }
-
- @Test
- public void addItemsToQueue_doesNotAddDuplicateUuids() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.setUuid(toUuid(1)).build());
- assertThat(player.getQueueSize()).isEqualTo(1);
- player.addItemsToQueue(
- itemBuilder.setUuid(toUuid(1)).build(), itemBuilder.setUuid(toUuid(2)).build());
- assertThat(player.getQueueSize()).isEqualTo(2);
- try {
- player.addItemsToQueue(
- itemBuilder.setUuid(toUuid(3)).build(), itemBuilder.setUuid(toUuid(3)).build());
- fail();
- } catch (IllegalArgumentException e) {
- // Expected.
- }
- }
-
- @Test
- public void moveItemInQueue_behavesAsExpected() throws JSONException {
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- MediaItem item3 = itemBuilder.setUuid(toUuid(3)).build();
- player.addItemsToQueue(item1, item2, item3);
- assertMediaItemQueue(item1, item2, item3);
- player.moveItemInQueue(/* index= */ 0, /* newIndex= */ 2);
- assertMediaItemQueue(item2, item3, item1);
- player.moveItemInQueue(/* index= */ 1, /* newIndex= */ 1);
- assertMediaItemQueue(item2, item3, item1);
- player.moveItemInQueue(/* index= */ 1, /* newIndex= */ 0);
- assertMediaItemQueue(item3, item2, item1);
-
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(ExoCastMessage.class);
- verify(sessionManager, times(4)).send(messageCaptor.capture());
- // First sent message is an "add" message.
- assertMessageMovesItem(
- /* message= */ messageCaptor.getAllValues().get(1), item1, /* index= */ 2);
- assertMessageMovesItem(
- /* message= */ messageCaptor.getAllValues().get(2), item3, /* index= */ 1);
- assertMessageMovesItem(
- /* message= */ messageCaptor.getAllValues().get(3), item3, /* index= */ 0);
- }
-
- @Test
- public void moveItemInQueue_moveBeforeToAfter_masksWindowIndexAsExpected() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(1);
- player.moveItemInQueue(/* index= */ 0, /* newIndex= */ 1);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- }
-
- @Test
- public void moveItemInQueue_moveAfterToBefore_masksWindowIndexAsExpected() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- player.moveItemInQueue(/* index= */ 1, /* newIndex= */ 0);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(1);
- }
-
- @Test
- public void moveItemInQueue_moveCurrent_masksWindowIndexAsExpected() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- player.moveItemInQueue(/* index= */ 0, /* newIndex= */ 2);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(2);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(2);
- }
-
- @Test
- public void removeItemsFromQueue_masksMediaQueue() throws JSONException {
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- MediaItem item3 = itemBuilder.setUuid(toUuid(3)).build();
- MediaItem item4 = itemBuilder.setUuid(toUuid(4)).build();
- MediaItem item5 = itemBuilder.setUuid(toUuid(5)).build();
- player.addItemsToQueue(item1, item2, item3, item4, item5);
- assertMediaItemQueue(item1, item2, item3, item4, item5);
-
- player.removeItemFromQueue(2);
- assertMediaItemQueue(item1, item2, item4, item5);
-
- player.removeRangeFromQueue(1, 3);
- assertMediaItemQueue(item1, item5);
-
- player.clearQueue();
- assertMediaItemQueue();
-
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(ExoCastMessage.class);
- verify(sessionManager, times(4)).send(messageCaptor.capture());
- // First sent message is an "add" message.
- assertMessageRemovesItems(
- messageCaptor.getAllValues().get(1), Collections.singletonList(item3));
- assertMessageRemovesItems(messageCaptor.getAllValues().get(2), Arrays.asList(item2, item4));
- assertMessageRemovesItems(messageCaptor.getAllValues().get(3), Arrays.asList(item1, item5));
- }
-
- @Test
- public void removeRangeFromQueue_beforeCurrentItem_masksWindowIndexAsExpected() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 2, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(2);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(2);
- player.removeRangeFromQueue(/* indexFrom= */ 0, /* indexExclusiveTo= */ 2);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- }
-
- @Test
- public void removeRangeFromQueue_currentItem_masksWindowIndexAsExpected() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(1);
- player.removeRangeFromQueue(/* indexFrom= */ 0, /* indexExclusiveTo= */ 2);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- }
-
- @Test
- public void removeRangeFromQueue_currentItemWhichIsLast_transitionsToEnded() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(1);
- player.removeRangeFromQueue(/* indexFrom= */ 1, /* indexExclusiveTo= */ 3);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_ENDED);
- }
-
- @Test
- public void clearQueue_resetsPlaybackPosition() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build(), itemBuilder.build());
- player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 500);
-
- assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(1);
- player.clearQueue();
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_ENDED);
- }
-
- @Test
- public void prepare_emptyQueue_transitionsToEnded() {
- player.prepare();
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_ENDED);
- verify(playerEventListener).onPlayerStateChanged(/* playWhenReady=*/ false, Player.STATE_ENDED);
- }
-
- @Test
- public void prepare_withQueue_transitionsToBuffering() {
- player.addItemsToQueue(itemBuilder.build());
- player.prepare();
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_BUFFERING);
- verify(playerEventListener)
- .onPlayerStateChanged(/* playWhenReady=*/ false, Player.STATE_BUFFERING);
- ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Timeline.class);
- verify(playerEventListener)
- .onTimelineChanged(
- argumentCaptor.capture(),
- /* manifest= */ isNull(),
- eq(Player.TIMELINE_CHANGE_REASON_PREPARED));
- assertThat(argumentCaptor.getValue().getWindowCount()).isEqualTo(1);
- }
-
- @Test
- public void stop_withoutReset_leavesCurrentTimeline() {
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE);
- player.addItemsToQueue(itemBuilder.setUuid(toUuid(1)).build());
- player.prepare();
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_BUFFERING);
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET);
- verify(playerEventListener)
- .onPlayerStateChanged(/* playWhenReady =*/ false, Player.STATE_BUFFERING);
- verify(playerEventListener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
- player.stop(/* reset= */ false);
- verify(playerEventListener).onPlayerStateChanged(/* playWhenReady =*/ false, Player.STATE_IDLE);
-
- ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Timeline.class);
- // Update for prepare.
- verify(playerEventListener)
- .onTimelineChanged(
- argumentCaptor.capture(),
- /* manifest= */ isNull(),
- eq(Player.TIMELINE_CHANGE_REASON_PREPARED));
- assertThat(argumentCaptor.getValue().getWindowCount()).isEqualTo(1);
-
- // Update for stop.
- verifyNoMoreInteractions(playerEventListener);
- assertThat(player.getCurrentTimeline().getWindowCount()).isEqualTo(1);
- }
-
- @Test
- public void stop_withReset_clearsQueue() {
- player.prepare();
- player.addItemsToQueue(itemBuilder.setUuid(toUuid(1)).build());
- verify(playerEventListener)
- .onTimelineChanged(
- any(Timeline.class), isNull(), eq(Player.TIMELINE_CHANGE_REASON_DYNAMIC));
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ C.TIME_UNSET);
- verify(playerEventListener)
- .onPlayerStateChanged(/* playWhenReady =*/ false, Player.STATE_BUFFERING);
- player.stop(/* reset= */ true);
- verify(playerEventListener).onPlayerStateChanged(/* playWhenReady =*/ false, Player.STATE_IDLE);
-
- // Update for add.
- ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Timeline.class);
- verify(playerEventListener)
- .onTimelineChanged(
- argumentCaptor.capture(),
- /* manifest= */ isNull(),
- eq(Player.TIMELINE_CHANGE_REASON_DYNAMIC));
- assertThat(argumentCaptor.getValue().getWindowCount()).isEqualTo(1);
-
- // Update for stop.
- verify(playerEventListener)
- .onTimelineChanged(
- argumentCaptor.capture(),
- /* manifest= */ isNull(),
- eq(Player.TIMELINE_CHANGE_REASON_RESET));
- assertThat(argumentCaptor.getValue().getWindowCount()).isEqualTo(0);
-
- assertThat(player.getCurrentTimeline().isEmpty()).isTrue();
- }
-
- @Test
- public void getCurrentTimeline_masksRemoteUpdates() {
- player.prepare();
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- assertThat(player.getCurrentTimeline().isEmpty()).isTrue();
- player.addItemsToQueue(item1, item2);
-
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Timeline.class);
- verify(playerEventListener)
- .onTimelineChanged(
- messageCaptor.capture(),
- /* manifest= */ isNull(),
- eq(Player.TIMELINE_CHANGE_REASON_DYNAMIC));
- Timeline reportedTimeline = messageCaptor.getValue();
- assertThat(reportedTimeline).isSameInstanceAs(player.getCurrentTimeline());
- assertThat(reportedTimeline.getWindowCount()).isEqualTo(2);
- assertThat(reportedTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()).durationUs)
- .isEqualTo(C.TIME_UNSET);
- assertThat(reportedTimeline.getWindow(/* windowIndex= */ 1, new Timeline.Window()).durationUs)
- .isEqualTo(C.TIME_UNSET);
- }
-
- @Test
- public void getCurrentTimeline_exposesReceiverState() {
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1)
- .setPlaybackState(Player.STATE_BUFFERING)
- .setItems(Arrays.asList(item1, item2))
- .setShuffleOrder(Arrays.asList(1, 0))
- .build());
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Timeline.class);
- verify(playerEventListener)
- .onTimelineChanged(
- messageCaptor.capture(),
- /* manifest= */ isNull(),
- eq(Player.TIMELINE_CHANGE_REASON_DYNAMIC));
- Timeline reportedTimeline = messageCaptor.getValue();
- assertThat(reportedTimeline).isSameInstanceAs(player.getCurrentTimeline());
- assertThat(reportedTimeline.getWindowCount()).isEqualTo(2);
- assertThat(reportedTimeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()).durationUs)
- .isEqualTo(C.TIME_UNSET);
- assertThat(reportedTimeline.getWindow(/* windowIndex= */ 1, new Timeline.Window()).durationUs)
- .isEqualTo(C.TIME_UNSET);
- }
-
- @Test
- public void timelineUpdateFromReceiver_matchesLocalState_doesNotCallEventLsitener() {
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- MediaItem item3 = itemBuilder.setUuid(toUuid(3)).build();
- MediaItem item4 = itemBuilder.setUuid(toUuid(4)).build();
-
- MediaItemInfo.Period period1 =
- new MediaItemInfo.Period("id1", /* durationUs= */ 1000000L, /* positionInWindowUs= */ 0);
- MediaItemInfo.Period period2 =
- new MediaItemInfo.Period(
- "id2", /* durationUs= */ 1000000L, /* positionInWindowUs= */ 1000000L);
- MediaItemInfo.Period period3 =
- new MediaItemInfo.Period(
- "id3", /* durationUs= */ 1000000L, /* positionInWindowUs= */ 2000000L);
- HashMap mediaItemInfoMap1 = new HashMap<>();
- mediaItemInfoMap1.put(
- toUuid(1),
- new MediaItemInfo(
- /* windowDurationUs= */ 3000L,
- /* defaultStartPositionUs= */ 10,
- /* periods= */ Arrays.asList(period1, period2, period3),
- /* positionInFirstPeriodUs= */ 0,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- mediaItemInfoMap1.put(
- toUuid(3),
- new MediaItemInfo(
- /* windowDurationUs= */ 2000L,
- /* defaultStartPositionUs= */ 10,
- /* periods= */ Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 500,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
-
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(1)
- .setPlaybackState(Player.STATE_BUFFERING)
- .setItems(Arrays.asList(item1, item2, item3, item4))
- .setShuffleOrder(Arrays.asList(1, 0, 2, 3))
- .setMediaItemsInformation(mediaItemInfoMap1)
- .build());
- verify(playerEventListener)
- .onTimelineChanged(
- any(), /* manifest= */ isNull(), eq(Player.TIMELINE_CHANGE_REASON_DYNAMIC));
- verify(playerEventListener)
- .onPlayerStateChanged(
- /* playWhenReady= */ false, /* playbackState= */ Player.STATE_BUFFERING);
-
- HashMap mediaItemInfoMap2 = new HashMap<>(mediaItemInfoMap1);
- mediaItemInfoMap2.put(
- toUuid(5),
- new MediaItemInfo(
- /* windowDurationUs= */ 5,
- /* defaultStartPositionUs= */ 0,
- /* periods= */ Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 500,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
-
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(1).setMediaItemsInformation(mediaItemInfoMap2).build());
- verifyNoMoreInteractions(playerEventListener);
- }
-
- @Test
- public void getPeriodIndex_producesExpectedOutput() {
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- MediaItem item3 = itemBuilder.setUuid(toUuid(3)).build();
- MediaItem item4 = itemBuilder.setUuid(toUuid(4)).build();
-
- MediaItemInfo.Period period1 =
- new MediaItemInfo.Period("id1", /* durationUs= */ 1000000L, /* positionInWindowUs= */ 0);
- MediaItemInfo.Period period2 =
- new MediaItemInfo.Period(
- "id2", /* durationUs= */ 1000000L, /* positionInWindowUs= */ 1000000L);
- MediaItemInfo.Period period3 =
- new MediaItemInfo.Period(
- "id3", /* durationUs= */ 1000000L, /* positionInWindowUs= */ 2000000L);
- HashMap mediaItemInfoMap = new HashMap<>();
- mediaItemInfoMap.put(
- toUuid(1),
- new MediaItemInfo(
- /* windowDurationUs= */ 3000L,
- /* defaultStartPositionUs= */ 10,
- /* periods= */ Arrays.asList(period1, period2, period3),
- /* positionInFirstPeriodUs= */ 0,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- mediaItemInfoMap.put(
- toUuid(3),
- new MediaItemInfo(
- /* windowDurationUs= */ 2000L,
- /* defaultStartPositionUs= */ 10,
- /* periods= */ Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 500,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
-
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1L)
- .setPlaybackState(Player.STATE_BUFFERING)
- .setItems(Arrays.asList(item1, item2, item3, item4))
- .setShuffleOrder(Arrays.asList(1, 0, 3, 2))
- .setMediaItemsInformation(mediaItemInfoMap)
- .setPlaybackPosition(
- /* currentPlayingItemUuid= */ item3.uuid,
- /* currentPlayingPeriodId= */ "id2",
- /* currentPlaybackPositionMs= */ 500L)
- .build());
-
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(5);
- player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 0L);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(3);
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 1500L);
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(1);
- }
-
- @Test
- public void exoCastPlayer_propagatesPlayerStateFromReceiver() {
- ReceiverAppStateUpdate.Builder builder =
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1);
-
- // The first idle state update should be discarded, since it matches the current state.
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- builder.setPlaybackState(Player.STATE_IDLE).build());
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- builder.setPlaybackState(Player.STATE_BUFFERING).build());
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- builder.setPlaybackState(Player.STATE_READY).build());
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- builder.setPlaybackState(Player.STATE_ENDED).build());
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Integer.class);
- verify(playerEventListener, times(3))
- .onPlayerStateChanged(/* playWhenReady= */ eq(false), messageCaptor.capture());
- List states = messageCaptor.getAllValues();
- assertThat(states).hasSize(3);
- assertThat(states)
- .isEqualTo(Arrays.asList(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_ENDED));
- }
-
- @Test
- public void setPlayWhenReady_changedLocally_notifiesListeners() {
- player.setPlayWhenReady(false);
- verify(playerEventListener, never()).onPlayerStateChanged(false, Player.STATE_IDLE);
- player.setPlayWhenReady(true);
- verify(playerEventListener).onPlayerStateChanged(true, Player.STATE_IDLE);
- player.setPlayWhenReady(false);
- verify(playerEventListener).onPlayerStateChanged(false, Player.STATE_IDLE);
- }
-
- @Test
- public void setPlayWhenReady_changedRemotely_notifiesListeners() {
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 0).setPlayWhenReady(true).build());
- verify(playerEventListener)
- .onPlayerStateChanged(/* playWhenReady= */ true, /* playbackState= */ Player.STATE_IDLE);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 0).setPlayWhenReady(true).build());
- verifyNoMoreInteractions(playerEventListener);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 0).setPlayWhenReady(false).build());
- verify(playerEventListener)
- .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_IDLE);
- verifyNoMoreInteractions(playerEventListener);
- }
-
- @Test
- public void getPlayWhenReady_masksRemoteUpdates() {
- when(sessionManager.send(any(ExoCastMessage.class))).thenReturn(3L);
- player.setPlayWhenReady(true);
- verify(playerEventListener)
- .onPlayerStateChanged(/* playWhenReady= */ true, /* playbackState= */ Player.STATE_IDLE);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 2).setPlayWhenReady(false).build());
- verifyNoMoreInteractions(playerEventListener);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 3).setPlayWhenReady(true).build());
- verifyNoMoreInteractions(playerEventListener);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 3).setPlayWhenReady(false).build());
- verify(playerEventListener)
- .onPlayerStateChanged(/* playWhenReady= */ false, /* playbackState= */ Player.STATE_IDLE);
- }
-
- @Test
- public void setRepeatMode_changedLocally_notifiesListeners() {
- player.setRepeatMode(Player.REPEAT_MODE_OFF);
- verifyNoMoreInteractions(playerEventListener);
- player.setRepeatMode(Player.REPEAT_MODE_ONE);
- verify(playerEventListener).onRepeatModeChanged(Player.REPEAT_MODE_ONE);
- player.setRepeatMode(Player.REPEAT_MODE_ONE);
- verifyNoMoreInteractions(playerEventListener);
- }
-
- @Test
- public void setRepeatMode_changedRemotely_notifiesListeners() {
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 0)
- .setRepeatMode(Player.REPEAT_MODE_ONE)
- .build());
- verify(playerEventListener).onRepeatModeChanged(Player.REPEAT_MODE_ONE);
- assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ONE);
- }
-
- @Test
- public void getRepeatMode_masksRemoteUpdates() {
- when(sessionManager.send(any(ExoCastMessage.class))).thenReturn(3L);
- player.setRepeatMode(Player.REPEAT_MODE_ALL);
- assertThat(player.getRepeatMode()).isEqualTo(Player.REPEAT_MODE_ALL);
- verify(playerEventListener).onRepeatModeChanged(Player.REPEAT_MODE_ALL);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 2)
- .setRepeatMode(Player.REPEAT_MODE_ONE)
- .build());
- verifyNoMoreInteractions(playerEventListener);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 3)
- .setRepeatMode(Player.REPEAT_MODE_ONE)
- .build());
- verify(playerEventListener).onRepeatModeChanged(Player.REPEAT_MODE_ONE);
- }
-
- @Test
- public void getPlaybackPosition_withStateChanges_producesExpectedOutput() {
- UUID uuid = toUuid(1);
- HashMap mediaItemInfoMap = new HashMap<>();
-
- MediaItemInfo.Period period1 = new MediaItemInfo.Period("id1", 1000L, 0);
- MediaItemInfo.Period period2 = new MediaItemInfo.Period("id2", 1000L, 0);
- MediaItemInfo.Period period3 = new MediaItemInfo.Period("id3", 1000L, 0);
- mediaItemInfoMap.put(
- uuid,
- new MediaItemInfo(
- /* windowDurationUs= */ 1000L,
- /* defaultStartPositionUs= */ 10,
- /* periods= */ Arrays.asList(period1, period2, period3),
- /* positionInFirstPeriodUs= */ 500,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
-
- when(sessionManager.send(any(ExoCastMessage.class))).thenReturn(1L);
- player.addItemsToQueue(itemBuilder.setUuid(uuid).build());
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1)
- .setPlaybackState(Player.STATE_BUFFERING)
- .setMediaItemsInformation(mediaItemInfoMap)
- .setPlaybackPosition(uuid, "id2", /* currentPlaybackPositionMs= */ 1000L)
- .build());
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPosition()).isEqualTo(1000L);
- clock.advanceTime(/* timeDiffMs= */ 1L);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1)
- .setPlaybackState(Player.STATE_READY)
- .build());
- // Play when ready is still false, so position should not change.
- assertThat(player.getCurrentPosition()).isEqualTo(1000L);
- player.setPlayWhenReady(true);
- clock.advanceTime(1);
- assertThat(player.getCurrentPosition()).isEqualTo(1001L);
- clock.advanceTime(1);
- assertThat(player.getCurrentPosition()).isEqualTo(1002L);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1)
- .setPlaybackState(Player.STATE_BUFFERING)
- .setMediaItemsInformation(mediaItemInfoMap)
- .setPlaybackPosition(uuid, "id2", /* currentPlaybackPositionMs= */ 1010L)
- .build());
- clock.advanceTime(1);
- assertThat(player.getCurrentPosition()).isEqualTo(1010L);
- clock.advanceTime(1);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1)
- .setPlaybackState(Player.STATE_READY)
- .setMediaItemsInformation(mediaItemInfoMap)
- .setPlaybackPosition(uuid, "id2", /* currentPlaybackPositionMs= */ 1011L)
- .build());
- clock.advanceTime(10);
- assertThat(player.getCurrentPosition()).isEqualTo(1021L);
- }
-
- @Test
- public void getPlaybackPosition_withNonDefaultPlaybackSpeed_producesExpectedOutput() {
- MediaItem item = itemBuilder.setUuid(toUuid(1)).build();
- MediaItemInfo info =
- new MediaItemInfo(
- /* windowDurationUs= */ 10000000,
- /* defaultStartPositionUs= */ 3000000,
- /* periods= */ Collections.singletonList(
- new MediaItemInfo.Period(
- /* id= */ "id", /* durationUs= */ 10000000, /* positionInWindowUs= */ 0)),
- /* positionInFirstPeriodUs= */ 0,
- /* isSeekable= */ true,
- /* isDynamic= */ false);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1)
- .setMediaItemsInformation(Collections.singletonMap(toUuid(1), info))
- .setShuffleOrder(Collections.singletonList(0))
- .setItems(Collections.singletonList(item))
- .setPlaybackPosition(
- toUuid(1), /* currentPlayingPeriodId= */ "id", /* currentPlaybackPositionMs= */ 20L)
- .setPlaybackState(Player.STATE_READY)
- .setPlayWhenReady(true)
- .build());
- assertThat(player.getCurrentPeriodIndex()).isEqualTo(0);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPosition()).isEqualTo(20);
- clock.advanceTime(10);
- assertThat(player.getCurrentPosition()).isEqualTo(30);
- clock.advanceTime(10);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(1)
- .setPlaybackPosition(
- toUuid(1), /* currentPlayingPeriodId= */ "id", /* currentPlaybackPositionMs= */ 40L)
- .setPlaybackParameters(new PlaybackParameters(2))
- .build());
- clock.advanceTime(10);
- assertThat(player.getCurrentPosition()).isEqualTo(60);
- }
-
- @Test
- public void positionChanges_notifiesDiscontinuities() {
- UUID uuid = toUuid(1);
- HashMap mediaItemInfoMap = new HashMap<>();
-
- MediaItemInfo.Period period1 = new MediaItemInfo.Period("id1", 1000L, 0);
- MediaItemInfo.Period period2 = new MediaItemInfo.Period("id2", 1000L, 0);
- MediaItemInfo.Period period3 = new MediaItemInfo.Period("id3", 1000L, 0);
- mediaItemInfoMap.put(
- uuid,
- new MediaItemInfo(
- /* windowDurationUs= */ 1000L,
- /* defaultStartPositionUs= */ 10,
- /* periods= */ Arrays.asList(period1, period2, period3),
- /* positionInFirstPeriodUs= */ 500,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
-
- player.addItemsToQueue(itemBuilder.setUuid(uuid).build());
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 1)
- .setPlaybackState(Player.STATE_BUFFERING)
- .setMediaItemsInformation(mediaItemInfoMap)
- .setPlaybackPosition(uuid, "id2", /* currentPlaybackPositionMs= */ 1000L)
- .setDiscontinuityReason(Player.DISCONTINUITY_REASON_SEEK)
- .build());
- verify(playerEventListener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 999);
- verify(playerEventListener, times(2)).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
- }
-
- @Test
- public void setShuffleModeEnabled_changedLocally_notifiesListeners() {
- player.setShuffleModeEnabled(true);
- verify(playerEventListener).onShuffleModeEnabledChanged(true);
- player.setShuffleModeEnabled(true);
- verifyNoMoreInteractions(playerEventListener);
- }
-
- @Test
- public void setShuffleModeEnabled_changedRemotely_notifiesListeners() {
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 0)
- .setShuffleModeEnabled(true)
- .build());
- verify(playerEventListener).onShuffleModeEnabledChanged(true);
- assertThat(player.getShuffleModeEnabled()).isTrue();
- }
-
- @Test
- public void getShuffleMode_masksRemoteUpdates() {
- when(sessionManager.send(any(ExoCastMessage.class))).thenReturn(3L);
- player.setShuffleModeEnabled(true);
- assertThat(player.getShuffleModeEnabled()).isTrue();
- verify(playerEventListener).onShuffleModeEnabledChanged(true);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 2)
- .setShuffleModeEnabled(false)
- .build());
- verifyNoMoreInteractions(playerEventListener);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 3)
- .setShuffleModeEnabled(false)
- .build());
- verify(playerEventListener).onShuffleModeEnabledChanged(false);
- assertThat(player.getShuffleModeEnabled()).isFalse();
- }
-
- @Test
- public void seekTo_inIdle_doesNotChangePlaybackState() {
- player.prepare();
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_ENDED);
- player.addItemsToQueue(itemBuilder.build(), itemBuilder.build());
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_ENDED);
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_BUFFERING);
- player.stop(false);
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE);
- player.seekTo(/* windowIndex= */ 0, /* positionMs= */ 0);
- assertThat(player.getPlaybackState()).isEqualTo(Player.STATE_IDLE);
- }
-
- @Test
- public void seekTo_withTwoItems_producesExpectedMessage() {
- player.prepare();
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- player.addItemsToQueue(item1, item2);
- player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1000);
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(ExoCastMessage.class);
- verify(sessionManager, times(3)).send(messageCaptor.capture());
- // Messages should be prepare, add and seek.
- ExoCastMessage.SeekTo seekToMessage =
- (ExoCastMessage.SeekTo) messageCaptor.getAllValues().get(2);
- assertThat(seekToMessage.positionMs).isEqualTo(1000);
- assertThat(seekToMessage.uuid).isEqualTo(toUuid(2));
- }
-
- @Test
- public void seekTo_masksRemoteUpdates() {
- player.prepare();
- when(sessionManager.send(any(ExoCastMessage.class))).thenReturn(3L);
- MediaItem item1 = itemBuilder.setUuid(toUuid(1)).build();
- MediaItem item2 = itemBuilder.setUuid(toUuid(2)).build();
- player.addItemsToQueue(item1, item2);
- player.seekTo(/* windowIndex= */ 1, /* positionMs= */ 1000L);
- verify(playerEventListener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
- verify(playerEventListener)
- .onPlayerStateChanged(
- /* playWhenReady= */ false, /* playbackState= */ Player.STATE_BUFFERING);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
- assertThat(player.getCurrentPosition()).isEqualTo(1000);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 2)
- .setPlaybackPosition(toUuid(1), "id", 500L)
- .build());
- assertThat(player.getCurrentWindowIndex()).isEqualTo(1);
- assertThat(player.getCurrentPosition()).isEqualTo(1000);
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 3)
- .setPlaybackPosition(toUuid(1), "id", 500L)
- .setDiscontinuityReason(Player.DISCONTINUITY_REASON_SEEK)
- .build());
- verify(playerEventListener, times(2)).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
- assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
- assertThat(player.getCurrentPosition()).isEqualTo(500);
- }
-
- @Test
- public void setPlaybackParameters_producesExpectedMessage() {
- PlaybackParameters playbackParameters =
- new PlaybackParameters(/* speed= */ .5f, /* pitch= */ .25f, /* skipSilence= */ true);
- player.setPlaybackParameters(playbackParameters);
- ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(ExoCastMessage.class);
- verify(sessionManager).send(messageCaptor.capture());
- ExoCastMessage.SetPlaybackParameters message =
- (ExoCastMessage.SetPlaybackParameters) messageCaptor.getValue();
- assertThat(message.playbackParameters).isEqualTo(playbackParameters);
- }
-
- @Test
- public void getTrackSelectionParameters_doesNotOverrideUnexpectedFields() {
- when(sessionManager.send(any(ExoCastMessage.class))).thenReturn(3L);
- DefaultTrackSelector.Parameters parameters =
- DefaultTrackSelector.Parameters.DEFAULT
- .buildUpon()
- .setPreferredAudioLanguage("spa")
- .setMaxVideoSize(/* maxVideoWidth= */ 3, /* maxVideoHeight= */ 3)
- .build();
- player.setTrackSelectionParameters(parameters);
- TrackSelectionParameters returned =
- TrackSelectionParameters.DEFAULT.buildUpon().setPreferredAudioLanguage("deu").build();
- receiverAppStateListener.onStateUpdateFromReceiverApp(
- ReceiverAppStateUpdate.builder(/* sequenceNumber= */ 3)
- .setTrackSelectionParameters(returned)
- .build());
- DefaultTrackSelector.Parameters result =
- (DefaultTrackSelector.Parameters) player.getTrackSelectionParameters();
- assertThat(result.preferredAudioLanguage).isEqualTo("deu");
- assertThat(result.maxVideoHeight).isEqualTo(3);
- assertThat(result.maxVideoWidth).isEqualTo(3);
- }
-
- @Test
- public void testExoCast_getRendererType() {
- assertThat(player.getRendererCount()).isEqualTo(4);
- assertThat(player.getRendererType(/* index= */ 0)).isEqualTo(C.TRACK_TYPE_VIDEO);
- assertThat(player.getRendererType(/* index= */ 1)).isEqualTo(C.TRACK_TYPE_AUDIO);
- assertThat(player.getRendererType(/* index= */ 2)).isEqualTo(C.TRACK_TYPE_TEXT);
- assertThat(player.getRendererType(/* index= */ 3)).isEqualTo(C.TRACK_TYPE_METADATA);
- }
-
- private static UUID toUuid(long lowerBits) {
- return new UUID(0, lowerBits);
- }
-
- private void assertMediaItemQueue(MediaItem... mediaItemQueue) {
- assertThat(player.getQueueSize()).isEqualTo(mediaItemQueue.length);
- for (int i = 0; i < mediaItemQueue.length; i++) {
- assertThat(player.getQueueItem(i).uuid).isEqualTo(mediaItemQueue[i].uuid);
- }
- }
-
- private static void assertMessageAddsItems(
- ExoCastMessage message, int index, List mediaItems) throws JSONException {
- assertThat(message.method).isEqualTo(ExoCastConstants.METHOD_ADD_ITEMS);
- JSONObject args =
- new JSONObject(message.toJsonString(MOCK_SEQUENCE_NUMBER)).getJSONObject(KEY_ARGS);
- if (index != C.INDEX_UNSET) {
- assertThat(args.getInt(KEY_INDEX)).isEqualTo(index);
- } else {
- assertThat(args.has(KEY_INDEX)).isFalse();
- }
- JSONArray itemsAsJson = args.getJSONArray(KEY_ITEMS);
- assertThat(ReceiverAppStateUpdate.toMediaItemArrayList(itemsAsJson)).isEqualTo(mediaItems);
- }
-
- private static void assertMessageMovesItem(ExoCastMessage message, MediaItem item, int index)
- throws JSONException {
- assertThat(message.method).isEqualTo(ExoCastConstants.METHOD_MOVE_ITEM);
- JSONObject args =
- new JSONObject(message.toJsonString(MOCK_SEQUENCE_NUMBER)).getJSONObject(KEY_ARGS);
- assertThat(args.getString(KEY_UUID)).isEqualTo(item.uuid.toString());
- assertThat(args.getInt(KEY_INDEX)).isEqualTo(index);
- }
-
- private static void assertMessageRemovesItems(ExoCastMessage message, List items)
- throws JSONException {
- assertThat(message.method).isEqualTo(ExoCastConstants.METHOD_REMOVE_ITEMS);
- JSONObject args =
- new JSONObject(message.toJsonString(MOCK_SEQUENCE_NUMBER)).getJSONObject(KEY_ARGS);
- JSONArray uuidsAsJson = args.getJSONArray(KEY_UUIDS);
- for (int i = 0; i < uuidsAsJson.length(); i++) {
- assertThat(uuidsAsJson.getString(i)).isEqualTo(items.get(i).uuid.toString());
- }
- }
-}
diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastTimelineTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastTimelineTest.java
deleted file mode 100644
index f6084339e4..0000000000
--- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ExoCastTimelineTest.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.UUID;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit test for {@link ExoCastTimeline}. */
-@RunWith(AndroidJUnit4.class)
-public class ExoCastTimelineTest {
-
- private MediaItem mediaItem1;
- private MediaItem mediaItem2;
- private MediaItem mediaItem3;
- private MediaItem mediaItem4;
- private MediaItem mediaItem5;
-
- @Before
- public void setUp() {
- MediaItem.Builder builder = new MediaItem.Builder();
- mediaItem1 = builder.setUuid(asUUID(1)).build();
- mediaItem2 = builder.setUuid(asUUID(2)).build();
- mediaItem3 = builder.setUuid(asUUID(3)).build();
- mediaItem4 = builder.setUuid(asUUID(4)).build();
- mediaItem5 = builder.setUuid(asUUID(5)).build();
- }
-
- @Test
- public void getWindowCount_withNoItems_producesExpectedCount() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Collections.emptyList(), Collections.emptyMap(), new DefaultShuffleOrder(0));
-
- assertThat(timeline.getWindowCount()).isEqualTo(0);
- }
-
- @Test
- public void getWindowCount_withFiveItems_producesExpectedCount() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(5));
-
- assertThat(timeline.getWindowCount()).isEqualTo(5);
- }
-
- @Test
- public void getWindow_withNoMediaItemInfo_returnsEmptyWindow() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(5));
- Timeline.Window window = timeline.getWindow(2, new Timeline.Window(), /* setTag= */ true);
-
- assertThat(window.tag).isNull();
- assertThat(window.presentationStartTimeMs).isEqualTo(C.TIME_UNSET);
- assertThat(window.windowStartTimeMs).isEqualTo(C.TIME_UNSET);
- assertThat(window.isSeekable).isFalse();
- assertThat(window.isDynamic).isTrue();
- assertThat(window.defaultPositionUs).isEqualTo(0L);
- assertThat(window.durationUs).isEqualTo(C.TIME_UNSET);
- assertThat(window.firstPeriodIndex).isEqualTo(2);
- assertThat(window.lastPeriodIndex).isEqualTo(2);
- assertThat(window.positionInFirstPeriodUs).isEqualTo(0L);
- }
-
- @Test
- public void getWindow_withMediaItemInfo_returnsPopulatedWindow() {
- MediaItem populatedMediaItem = new MediaItem.Builder().setAttachment("attachment").build();
- HashMap mediaItemInfos = new HashMap<>();
- MediaItemInfo.Period period1 =
- new MediaItemInfo.Period("id1", /* durationUs= */ 1000000L, /* positionInWindowUs= */ 0L);
- MediaItemInfo.Period period2 =
- new MediaItemInfo.Period(
- "id2", /* durationUs= */ 5000000L, /* positionInWindowUs= */ 1000000L);
- mediaItemInfos.put(
- populatedMediaItem.uuid,
- new MediaItemInfo(
- /* windowDurationUs= */ 4000000L,
- /* defaultStartPositionUs= */ 20L,
- Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 500L,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, populatedMediaItem),
- mediaItemInfos,
- new DefaultShuffleOrder(5));
- Timeline.Window window = timeline.getWindow(4, new Timeline.Window(), /* setTag= */ true);
-
- assertThat(window.tag).isSameInstanceAs(populatedMediaItem.attachment);
- assertThat(window.presentationStartTimeMs).isEqualTo(C.TIME_UNSET);
- assertThat(window.windowStartTimeMs).isEqualTo(C.TIME_UNSET);
- assertThat(window.isSeekable).isTrue();
- assertThat(window.isDynamic).isFalse();
- assertThat(window.defaultPositionUs).isEqualTo(20L);
- assertThat(window.durationUs).isEqualTo(4000000L);
- assertThat(window.firstPeriodIndex).isEqualTo(4);
- assertThat(window.lastPeriodIndex).isEqualTo(5);
- assertThat(window.positionInFirstPeriodUs).isEqualTo(500L);
- }
-
- @Test
- public void getPeriodCount_producesExpectedOutput() {
- HashMap mediaItemInfos = new HashMap<>();
- MediaItemInfo.Period period1 =
- new MediaItemInfo.Period(
- "id1", /* durationUs= */ 5000000L, /* positionInWindowUs= */ 1000000L);
- MediaItemInfo.Period period2 =
- new MediaItemInfo.Period(
- "id2", /* durationUs= */ 5000000L, /* positionInWindowUs= */ 6000000L);
- mediaItemInfos.put(
- asUUID(2),
- new MediaItemInfo(
- /* windowDurationUs= */ 7000000L,
- /* defaultStartPositionUs= */ 20L,
- Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 0L,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- mediaItemInfos,
- new DefaultShuffleOrder(5));
-
- assertThat(timeline.getPeriodCount()).isEqualTo(6);
- }
-
- @Test
- public void getPeriod_forPopulatedPeriod_producesExpectedOutput() {
- HashMap mediaItemInfos = new HashMap<>();
- MediaItemInfo.Period period1 =
- new MediaItemInfo.Period(
- "id1", /* durationUs= */ 4000000L, /* positionInWindowUs= */ 1000000L);
- MediaItemInfo.Period period2 =
- new MediaItemInfo.Period(
- "id2", /* durationUs= */ 5000000L, /* positionInWindowUs= */ 6000000L);
- mediaItemInfos.put(
- asUUID(5),
- new MediaItemInfo(
- /* windowDurationUs= */ 7000000L,
- /* defaultStartPositionUs= */ 20L,
- Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 0L,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- mediaItemInfos,
- new DefaultShuffleOrder(5));
- Timeline.Period period =
- timeline.getPeriod(/* periodIndex= */ 5, new Timeline.Period(), /* setIds= */ true);
- Object periodUid = timeline.getUidOfPeriod(/* periodIndex= */ 5);
-
- assertThat(period.durationUs).isEqualTo(5000000L);
- assertThat(period.windowIndex).isEqualTo(4);
- assertThat(period.id).isEqualTo("id2");
- assertThat(period.uid).isEqualTo(periodUid);
- }
-
- @Test
- public void getPeriod_forEmptyPeriod_producesExpectedOutput() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(5));
- Timeline.Period period = timeline.getPeriod(2, new Timeline.Period(), /* setIds= */ true);
- Object uid = timeline.getUidOfPeriod(/* periodIndex= */ 2);
-
- assertThat(period.durationUs).isEqualTo(C.TIME_UNSET);
- assertThat(period.windowIndex).isEqualTo(2);
- assertThat(period.id).isEqualTo(MediaItemInfo.EMPTY.periods.get(0).id);
- assertThat(period.uid).isEqualTo(uid);
- }
-
- @Test
- public void getIndexOfPeriod_worksAcrossDifferentTimelines() {
- MediaItemInfo.Period period1 =
- new MediaItemInfo.Period(
- "id1", /* durationUs= */ 4000000L, /* positionInWindowUs= */ 1000000L);
- MediaItemInfo.Period period2 =
- new MediaItemInfo.Period(
- "id2", /* durationUs= */ 5000000L, /* positionInWindowUs= */ 1000000L);
-
- HashMap mediaItemInfos1 = new HashMap<>();
- mediaItemInfos1.put(
- asUUID(1),
- new MediaItemInfo(
- /* windowDurationUs= */ 5000000L,
- /* defaultStartPositionUs= */ 20L,
- Collections.singletonList(period2),
- /* positionInFirstPeriodUs= */ 0L,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- ExoCastTimeline timeline1 =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2), mediaItemInfos1, new DefaultShuffleOrder(2));
-
- HashMap mediaItemInfos2 = new HashMap<>();
- mediaItemInfos2.put(
- asUUID(1),
- new MediaItemInfo(
- /* windowDurationUs= */ 7000000L,
- /* defaultStartPositionUs= */ 20L,
- Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 0L,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- ExoCastTimeline timeline2 =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem2, mediaItem1, mediaItem3, mediaItem4, mediaItem5),
- mediaItemInfos2,
- new DefaultShuffleOrder(5));
- Object uidOfFirstPeriod = timeline1.getUidOfPeriod(0);
-
- assertThat(timeline1.getIndexOfPeriod(uidOfFirstPeriod)).isEqualTo(0);
- assertThat(timeline2.getIndexOfPeriod(uidOfFirstPeriod)).isEqualTo(2);
- }
-
- @Test
- public void getIndexOfPeriod_forLastPeriod_producesExpectedOutput() {
- MediaItemInfo.Period period1 =
- new MediaItemInfo.Period(
- "id1", /* durationUs= */ 4000000L, /* positionInWindowUs= */ 1000000L);
- MediaItemInfo.Period period2 =
- new MediaItemInfo.Period(
- "id2", /* durationUs= */ 5000000L, /* positionInWindowUs= */ 1000000L);
-
- HashMap mediaItemInfos1 = new HashMap<>();
- mediaItemInfos1.put(
- asUUID(5),
- new MediaItemInfo(
- /* windowDurationUs= */ 4000000L,
- /* defaultStartPositionUs= */ 20L,
- Collections.singletonList(period2),
- /* positionInFirstPeriodUs= */ 0L,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- ExoCastTimeline singlePeriodTimeline =
- ExoCastTimeline.createTimelineFor(
- Collections.singletonList(mediaItem5), mediaItemInfos1, new DefaultShuffleOrder(1));
- Object periodUid = singlePeriodTimeline.getUidOfPeriod(0);
-
- HashMap mediaItemInfos2 = new HashMap<>();
- mediaItemInfos2.put(
- asUUID(5),
- new MediaItemInfo(
- /* windowDurationUs= */ 7000000L,
- /* defaultStartPositionUs= */ 20L,
- Arrays.asList(period1, period2),
- /* positionInFirstPeriodUs= */ 0L,
- /* isSeekable= */ true,
- /* isDynamic= */ false));
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- mediaItemInfos2,
- new DefaultShuffleOrder(5));
-
- assertThat(timeline.getIndexOfPeriod(periodUid)).isEqualTo(5);
- }
-
- @Test
- public void getUidOfPeriod_withInvalidUid_returnsUnsetIndex() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(/* length= */ 5));
-
- assertThat(timeline.getIndexOfPeriod(new Object())).isEqualTo(C.INDEX_UNSET);
- }
-
- @Test
- public void getFirstWindowIndex_returnsIndexAccordingToShuffleMode() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(new int[] {1, 2, 0, 4, 3}, /* randomSeed= */ 0));
-
- assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ false)).isEqualTo(0);
- assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(1);
- }
-
- @Test
- public void getLastWindowIndex_returnsIndexAccordingToShuffleMode() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(new int[] {1, 2, 0, 4, 3}, /* randomSeed= */ 0));
-
- assertThat(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ false)).isEqualTo(4);
- assertThat(timeline.getLastWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(3);
- }
-
- @Test
- public void getNextWindowIndex_repeatModeOne_returnsSameIndex() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(5));
-
- for (int i = 0; i < 5; i++) {
- assertThat(
- timeline.getNextWindowIndex(
- i, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true))
- .isEqualTo(i);
- assertThat(
- timeline.getNextWindowIndex(
- i, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false))
- .isEqualTo(i);
- }
- }
-
- @Test
- public void getNextWindowIndex_onLastIndex_returnsExpectedIndex() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(new int[] {1, 2, 0, 4, 3}, /* randomSeed= */ 0));
-
- // Shuffle mode disabled:
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 4, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false))
- .isEqualTo(C.INDEX_UNSET);
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 4, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false))
- .isEqualTo(0);
- // Shuffle mode enabled:
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 3, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true))
- .isEqualTo(C.INDEX_UNSET);
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 3, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true))
- .isEqualTo(1);
- }
-
- @Test
- public void getNextWindowIndex_inMiddleOfQueue_returnsNextIndex() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(new int[] {1, 2, 0, 4, 3}, /* randomSeed= */ 0));
-
- // Shuffle mode disabled:
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 2, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false))
- .isEqualTo(3);
- // Shuffle mode enabled:
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 2, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true))
- .isEqualTo(0);
- }
-
- @Test
- public void getPreviousWindowIndex_repeatModeOne_returnsSameIndex() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(new int[] {1, 2, 0, 4, 3}, /* randomSeed= */ 0));
-
- for (int i = 0; i < 5; i++) {
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ i, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ true))
- .isEqualTo(i);
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ i, Player.REPEAT_MODE_ONE, /* shuffleModeEnabled= */ false))
- .isEqualTo(i);
- }
- }
-
- @Test
- public void getPreviousWindowIndex_onFirstIndex_returnsExpectedIndex() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(new int[] {1, 2, 0, 4, 3}, /* randomSeed= */ 0));
-
- // Shuffle mode disabled:
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ 0, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false))
- .isEqualTo(C.INDEX_UNSET);
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ 0, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false))
- .isEqualTo(4);
- // Shuffle mode enabled:
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ 1, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true))
- .isEqualTo(C.INDEX_UNSET);
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ 1, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ true))
- .isEqualTo(3);
- }
-
- @Test
- public void getPreviousWindowIndex_inMiddleOfQueue_returnsPreviousIndex() {
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(mediaItem1, mediaItem2, mediaItem3, mediaItem4, mediaItem5),
- Collections.emptyMap(),
- new DefaultShuffleOrder(new int[] {1, 2, 0, 4, 3}, /* randomSeed= */ 0));
-
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ 4, Player.REPEAT_MODE_ALL, /* shuffleModeEnabled= */ false))
- .isEqualTo(3);
- assertThat(
- timeline.getPreviousWindowIndex(
- /* windowIndex= */ 4, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ true))
- .isEqualTo(0);
- }
-
- private static UUID asUUID(long number) {
- return new UUID(/* mostSigBits= */ 0L, number);
- }
-}
diff --git a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ReceiverAppStateUpdateTest.java b/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ReceiverAppStateUpdateTest.java
deleted file mode 100644
index fbe936a016..0000000000
--- a/extensions/cast/src/test/java/com/google/android/exoplayer2/ext/cast/ReceiverAppStateUpdateTest.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * Copyright (C) 2018 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.cast;
-
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DEFAULT_START_POSITION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DISCONTINUITY_REASON;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_DURATION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ERROR_MESSAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_ID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_IS_DYNAMIC;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_IS_LOADING;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_IS_SEEKABLE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MEDIA_ITEMS_INFO;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_MEDIA_QUEUE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PERIODS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PERIOD_ID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PITCH;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAYBACK_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAYBACK_POSITION;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAYBACK_STATE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PLAY_WHEN_READY;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_POSITION_IN_FIRST_PERIOD_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_POSITION_MS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_AUDIO_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_PREFERRED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_REPEAT_MODE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SEQUENCE_NUMBER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_MODE_ENABLED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SHUFFLE_ORDER;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SKIP_SILENCE;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_SPEED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_TRACK_SELECTION_PARAMETERS;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_UUID;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.KEY_WINDOW_DURATION_US;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_DISCONTINUITY_REASON_SEEK;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_REPEAT_MODE_OFF;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_SELECTION_FLAG_FORCED;
-import static com.google.android.exoplayer2.ext.cast.ExoCastConstants.STR_STATE_BUFFERING;
-import static com.google.common.truth.Truth.assertThat;
-
-import android.net.Uri;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.google.android.exoplayer2.C;
-import com.google.android.exoplayer2.PlaybackParameters;
-import com.google.android.exoplayer2.Player;
-import com.google.android.exoplayer2.Timeline;
-import com.google.android.exoplayer2.source.ShuffleOrder;
-import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
-import com.google.android.exoplayer2.util.Util;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.UUID;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/** Unit tests for {@link ReceiverAppStateUpdate}. */
-@RunWith(AndroidJUnit4.class)
-public class ReceiverAppStateUpdateTest {
-
- private static final long MOCK_SEQUENCE_NUMBER = 1;
-
- @Test
- public void statusUpdate_withPlayWhenReady_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER).setPlayWhenReady(true).build();
- JSONObject stateMessage = createStateMessage().put(KEY_PLAY_WHEN_READY, true);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withPlaybackState_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setPlaybackState(Player.STATE_BUFFERING)
- .build();
- JSONObject stateMessage = createStateMessage().put(KEY_PLAYBACK_STATE, STR_STATE_BUFFERING);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withMediaQueue_producesExpectedUpdate() throws JSONException {
- HashMap requestHeaders = new HashMap<>();
- requestHeaders.put("key", "value");
- MediaItem.UriBundle media = new MediaItem.UriBundle(Uri.parse("www.media.com"), requestHeaders);
- MediaItem.DrmScheme drmScheme1 =
- new MediaItem.DrmScheme(
- C.WIDEVINE_UUID,
- new MediaItem.UriBundle(Uri.parse("www.widevine.com"), requestHeaders));
- MediaItem.DrmScheme drmScheme2 =
- new MediaItem.DrmScheme(
- C.PLAYREADY_UUID,
- new MediaItem.UriBundle(Uri.parse("www.playready.com"), requestHeaders));
- MediaItem item =
- new MediaItem.Builder()
- .setTitle("title")
- .setDescription("description")
- .setMedia(media)
- .setDrmSchemes(Arrays.asList(drmScheme1, drmScheme2))
- .setStartPositionUs(10)
- .setEndPositionUs(20)
- .build();
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setItems(Collections.singletonList(item))
- .build();
- JSONObject object =
- createStateMessage()
- .put(KEY_MEDIA_QUEUE, new JSONArray().put(ExoCastMessage.mediaItemAsJsonObject(item)));
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(object.toString())).isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withRepeatMode_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setRepeatMode(Player.REPEAT_MODE_OFF)
- .build();
- JSONObject stateMessage = createStateMessage().put(KEY_REPEAT_MODE, STR_REPEAT_MODE_OFF);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withShuffleModeEnabled_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER).setShuffleModeEnabled(false).build();
- JSONObject stateMessage = createStateMessage().put(KEY_SHUFFLE_MODE_ENABLED, false);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withIsLoading_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER).setIsLoading(true).build();
- JSONObject stateMessage = createStateMessage().put(KEY_IS_LOADING, true);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withPlaybackParameters_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setPlaybackParameters(
- new PlaybackParameters(
- /* speed= */ .5f, /* pitch= */ .25f, /* skipSilence= */ false))
- .build();
- JSONObject playbackParamsJson =
- new JSONObject().put(KEY_SPEED, .5).put(KEY_PITCH, .25).put(KEY_SKIP_SILENCE, false);
- JSONObject stateMessage = createStateMessage().put(KEY_PLAYBACK_PARAMETERS, playbackParamsJson);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withTrackSelectionParameters_producesExpectedUpdate()
- throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setTrackSelectionParameters(
- TrackSelectionParameters.DEFAULT
- .buildUpon()
- .setDisabledTextTrackSelectionFlags(
- C.SELECTION_FLAG_FORCED | C.SELECTION_FLAG_DEFAULT)
- .setPreferredAudioLanguage("esp")
- .setPreferredTextLanguage("deu")
- .setSelectUndeterminedTextLanguage(true)
- .build())
- .build();
-
- JSONArray selectionFlagsJson =
- new JSONArray()
- .put(ExoCastConstants.STR_SELECTION_FLAG_DEFAULT)
- .put(STR_SELECTION_FLAG_FORCED);
- JSONObject playbackParamsJson =
- new JSONObject()
- .put(KEY_DISABLED_TEXT_TRACK_SELECTION_FLAGS, selectionFlagsJson)
- .put(KEY_PREFERRED_AUDIO_LANGUAGE, "esp")
- .put(KEY_PREFERRED_TEXT_LANGUAGE, "deu")
- .put(KEY_SELECT_UNDETERMINED_TEXT_LANGUAGE, true);
- JSONObject object =
- createStateMessage().put(KEY_TRACK_SELECTION_PARAMETERS, playbackParamsJson);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(object.toString())).isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withError_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setErrorMessage("error message")
- .build();
- JSONObject stateMessage = createStateMessage().put(KEY_ERROR_MESSAGE, "error message");
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withPlaybackPosition_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setPlaybackPosition(
- new UUID(/* mostSigBits= */ 0, /* leastSigBits= */ 1), "period", 10L)
- .build();
- JSONObject positionJson =
- new JSONObject()
- .put(KEY_UUID, new UUID(0, 1))
- .put(KEY_POSITION_MS, 10)
- .put(KEY_PERIOD_ID, "period");
- JSONObject stateMessage = createStateMessage().put(KEY_PLAYBACK_POSITION, positionJson);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withDiscontinuity_producesExpectedUpdate() throws JSONException {
- ReceiverAppStateUpdate stateUpdate =
- ReceiverAppStateUpdate.builder(MOCK_SEQUENCE_NUMBER)
- .setPlaybackPosition(
- new UUID(/* mostSigBits= */ 0, /* leastSigBits= */ 1), "period", 10L)
- .setDiscontinuityReason(Player.DISCONTINUITY_REASON_SEEK)
- .build();
- JSONObject positionJson =
- new JSONObject()
- .put(KEY_UUID, new UUID(0, 1))
- .put(KEY_POSITION_MS, 10)
- .put(KEY_PERIOD_ID, "period")
- .put(KEY_DISCONTINUITY_REASON, STR_DISCONTINUITY_REASON_SEEK);
- JSONObject stateMessage = createStateMessage().put(KEY_PLAYBACK_POSITION, positionJson);
-
- assertThat(ReceiverAppStateUpdate.fromJsonMessage(stateMessage.toString()))
- .isEqualTo(stateUpdate);
- }
-
- @Test
- public void statusUpdate_withMediaItemInfo_producesExpectedTimeline() throws JSONException {
- MediaItem.Builder builder = new MediaItem.Builder();
- MediaItem item1 = builder.setUuid(new UUID(0, 1)).build();
- MediaItem item2 = builder.setUuid(new UUID(0, 2)).build();
-
- JSONArray periodsJson = new JSONArray();
- periodsJson
- .put(new JSONObject().put(KEY_ID, "id1").put(KEY_DURATION_US, 5000000L))
- .put(new JSONObject().put(KEY_ID, "id2").put(KEY_DURATION_US, 7000000L))
- .put(new JSONObject().put(KEY_ID, "id3").put(KEY_DURATION_US, 6000000L));
- JSONObject mediaItemInfoForUuid1 = new JSONObject();
- mediaItemInfoForUuid1
- .put(KEY_WINDOW_DURATION_US, 10000000L)
- .put(KEY_DEFAULT_START_POSITION_US, 1000000L)
- .put(KEY_PERIODS, periodsJson)
- .put(KEY_POSITION_IN_FIRST_PERIOD_US, 2000000L)
- .put(KEY_IS_DYNAMIC, false)
- .put(KEY_IS_SEEKABLE, true);
- JSONObject mediaItemInfoMapJson =
- new JSONObject().put(new UUID(0, 1).toString(), mediaItemInfoForUuid1);
-
- JSONObject receiverAppStateUpdateJson =
- createStateMessage().put(KEY_MEDIA_ITEMS_INFO, mediaItemInfoMapJson);
- ReceiverAppStateUpdate receiverAppStateUpdate =
- ReceiverAppStateUpdate.fromJsonMessage(receiverAppStateUpdateJson.toString());
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- Arrays.asList(item1, item2),
- receiverAppStateUpdate.mediaItemsInformation,
- new ShuffleOrder.DefaultShuffleOrder(
- /* shuffledIndices= */ new int[] {1, 0}, /* randomSeed= */ 0));
- Timeline.Window window0 =
- timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window(), /* setTag= */ true);
- Timeline.Window window1 =
- timeline.getWindow(/* windowIndex= */ 1, new Timeline.Window(), /* setTag= */ true);
- Timeline.Period[] periods = new Timeline.Period[4];
- for (int i = 0; i < 4; i++) {
- periods[i] =
- timeline.getPeriod(/* periodIndex= */ i, new Timeline.Period(), /* setIds= */ true);
- }
-
- assertThat(timeline.getWindowCount()).isEqualTo(2);
- assertThat(window0.positionInFirstPeriodUs).isEqualTo(2000000L);
- assertThat(window0.durationUs).isEqualTo(10000000L);
- assertThat(window0.isDynamic).isFalse();
- assertThat(window0.isSeekable).isTrue();
- assertThat(window0.defaultPositionUs).isEqualTo(1000000L);
- assertThat(window1.positionInFirstPeriodUs).isEqualTo(0L);
- assertThat(window1.durationUs).isEqualTo(C.TIME_UNSET);
- assertThat(window1.isDynamic).isTrue();
- assertThat(window1.isSeekable).isFalse();
- assertThat(window1.defaultPositionUs).isEqualTo(0L);
-
- assertThat(timeline.getPeriodCount()).isEqualTo(4);
- assertThat(periods[0].id).isEqualTo("id1");
- assertThat(periods[0].getPositionInWindowUs()).isEqualTo(-2000000L);
- assertThat(periods[0].durationUs).isEqualTo(5000000L);
- assertThat(periods[1].id).isEqualTo("id2");
- assertThat(periods[1].durationUs).isEqualTo(7000000L);
- assertThat(periods[1].getPositionInWindowUs()).isEqualTo(3000000L);
- assertThat(periods[2].id).isEqualTo("id3");
- assertThat(periods[2].durationUs).isEqualTo(6000000L);
- assertThat(periods[2].getPositionInWindowUs()).isEqualTo(10000000L);
- assertThat(periods[3].durationUs).isEqualTo(C.TIME_UNSET);
- }
-
- @Test
- public void statusUpdate_withShuffleOrder_producesExpectedTimeline() throws JSONException {
- MediaItem.Builder builder = new MediaItem.Builder();
- JSONObject receiverAppStateUpdateJson =
- createStateMessage().put(KEY_SHUFFLE_ORDER, new JSONArray(Arrays.asList(2, 3, 1, 0)));
- ReceiverAppStateUpdate receiverAppStateUpdate =
- ReceiverAppStateUpdate.fromJsonMessage(receiverAppStateUpdateJson.toString());
- ExoCastTimeline timeline =
- ExoCastTimeline.createTimelineFor(
- /* mediaItems= */ Arrays.asList(
- builder.build(), builder.build(), builder.build(), builder.build()),
- /* mediaItemInfoMap= */ Collections.emptyMap(),
- /* shuffleOrder= */ new ShuffleOrder.DefaultShuffleOrder(
- Util.toArray(receiverAppStateUpdate.shuffleOrder), /* randomSeed= */ 0));
-
- assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(2);
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 2,
- /* repeatMode= */ Player.REPEAT_MODE_OFF,
- /* shuffleModeEnabled= */ true))
- .isEqualTo(3);
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 3,
- /* repeatMode= */ Player.REPEAT_MODE_OFF,
- /* shuffleModeEnabled= */ true))
- .isEqualTo(1);
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 1,
- /* repeatMode= */ Player.REPEAT_MODE_OFF,
- /* shuffleModeEnabled= */ true))
- .isEqualTo(0);
- assertThat(
- timeline.getNextWindowIndex(
- /* windowIndex= */ 0,
- /* repeatMode= */ Player.REPEAT_MODE_OFF,
- /* shuffleModeEnabled= */ true))
- .isEqualTo(C.INDEX_UNSET);
- }
-
- private static JSONObject createStateMessage() throws JSONException {
- return new JSONObject().put(KEY_SEQUENCE_NUMBER, MOCK_SEQUENCE_NUMBER);
- }
-}