diff --git a/README.md b/README.md index cf07b97ad84..7ea16a8f429 100644 --- a/README.md +++ b/README.md @@ -94,15 +94,13 @@ to prevent build errors. Cloning the repository and depending on the modules locally is required when using some libraries. It's also a suitable approach if you want to make local -changes, or if you want to use the main branch. +changes, or if you want to use the `main` branch. -First, clone the repository into a local directory and checkout the desired -branch: +First, clone the repository into a local directory: ```sh git clone https://github.com/androidx/media.git cd media -git checkout main ``` Next, add the following to your project's `settings.gradle` file, replacing @@ -129,7 +127,7 @@ implementation project(':media-lib-ui') Development work happens on the `main` branch. Pull requests should normally be made to this branch. -We plan to add a release branch soon. +The `release` branch holds the most recent stable release. #### Using Android Studio diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8aed9050a46..204a19e7f6c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,30 @@ # Release notes -### 1.0.0-alpha02 (2022-03-09) +### 1.0.0-alpha03 (2022-03-14) + +This release corresponds to the +[ExoPlayer 2.17.1 release](https://github.com/google/ExoPlayer/releases/tag/r2.17.1). + +* Audio: + * Fix error checking audio capabilities for Dolby Atmos (E-AC3-JOC) in + HLS. +* Extractors: + * FMP4: Fix issue where emsg sample metadata could be output in the wrong + order for streams containing both v0 and v1 emsg atoms + ([#9996](https://github.com/google/ExoPlayer/issues/9996)). +* Text: + * Fix the interaction of `SingleSampleMediaSource.Factory.setTrackId` and + `MediaItem.SubtitleConfiguration.Builder.setId` to prioritise the + `SubtitleConfiguration` field and fall back to the `Factory` value if + it's not set + ([#10016](https://github.com/google/ExoPlayer/issues/10016)). +* Ad playback: + * Fix audio underruns between ad periods in live HLS SSAI streams. + +### 1.0.0-alpha02 (2022-03-02) + +This release corresponds to the +[ExoPlayer 2.17.0 release](https://github.com/google/ExoPlayer/releases/tag/r2.17.0). * Core Library: * Add protected method `DefaultRenderersFactory.getCodecAdapterFactory()` @@ -18,7 +42,7 @@ from a secure codec to another codec ([#8696](https://github.com/google/ExoPlayer/issues/8696)). * Add `MediaCodecAdapter.getMetrics()` to allow users obtain metrics data - from `MediaCodec`. + from `MediaCodec` ([#9766](https://github.com/google/ExoPlayer/issues/9766)). * Fix Maven dependency resolution ([#8353](https://github.com/google/ExoPlayer/issues/8353)). @@ -63,17 +87,17 @@ under sufficient network bandwidth even if playback is very close to the live edge ([#9784](https://github.com/google/ExoPlayer/issues/9784)). * Video: - * Fix decoder fallback logic for Dolby Vision - to use a compatible H264/H265 decoder if needed. + * Fix decoder fallback logic for Dolby Vision to use a compatible + H264/H265 decoder if needed. * Audio: - * Fix decoder fallback logic for Dolby Atmos (E-AC3-JOC) - to use a compatible E-AC3 decoder if needed. + * Fix decoder fallback logic for Dolby Atmos (E-AC3-JOC) to use a + compatible E-AC3 decoder if needed. * Change `AudioCapabilities` APIs to require passing explicitly `AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES` instead of `null`. * Allow customization of the `AudioTrack` buffer size calculation by - injecting an `AudioTrackBufferSizeProvider` to `DefaultAudioSink`. + injecting an `AudioTrackBufferSizeProvider` to `DefaultAudioSink` ([#8891](https://github.com/google/ExoPlayer/issues/8891)). - * Retry `AudioTrack` creation if the requested buffer size was > 1MB. + * Retry `AudioTrack` creation if the requested buffer size was > 1MB ([#9712](https://github.com/google/ExoPlayer/issues/9712)). * Extractors: * WAV: Add support for RF64 streams @@ -120,7 +144,8 @@ * Support the `forced-subtitle` track role ([#9727](https://github.com/google/ExoPlayer/issues/9727)). * Stop interpreting the `main` track role as `C.SELECTION_FLAG_DEFAULT`. - * Fix base URL exclusion logic for manifests that do not declare the DVB namespace ([#9856](https://github.com/google/ExoPlayer/issues/9856)). + * Fix base URL exclusion logic for manifests that do not declare the DVB + namespace ([#9856](https://github.com/google/ExoPlayer/issues/9856)). * Support relative `MPD.Location` URLs ([#9939](https://github.com/google/ExoPlayer/issues/9939)). * HLS: @@ -133,8 +158,6 @@ `HlsMediaSource.Factory.setAllowChunklessPreparation(false)`. * Support key-frame accurate seeking in HLS ([#2882](https://github.com/google/ExoPlayer/issues/2882)). - * Correctly populate `Format.label` for audio only HLS streams - ([#9608](https://github.com/google/ExoPlayer/issues/9608)). * RTSP: * Provide a client API to override the `SocketFactory` used for any server connection ([#9606](https://github.com/google/ExoPlayer/pull/9606)). @@ -154,12 +177,10 @@ * Fix potential NPE in `Transformer.getProgress` when releasing the muxer throws. * Add a demo app for applying transformations. - * The transformer module is no longer included by depending on - `com.google.android.exoplayer:exoplayer`. To continue using transformer, - add an additional dependency on - `com.google.android.exoplayer:exoplayer-transformer`. * MediaSession extension: - * By default, `MediaSessionConnector` now clears the playlist on stop. Apps that want the playlist to be retained can call `setClearMediaItemsOnStop(false)` on the connector. + * By default, `MediaSessionConnector` now clears the playlist on stop. + Apps that want the playlist to be retained can call + `setClearMediaItemsOnStop(false)` on the connector. * Cast extension: * Fix bug that prevented `CastPlayer` from calling `onIsPlayingChanged` correctly ([#9792](https://github.com/google/ExoPlayer/issues/9792)). @@ -178,38 +199,38 @@ ([#9528](https://github.com/google/ExoPlayer/issues/9528)). * Remove deprecated symbols: * Remove `Player.EventLister`. Use `Player.Listener` instead. - * Remove `MediaSourceFactory#setDrmSessionManager`, - `MediaSourceFactory#setDrmHttpDataSourceFactory`, and - `MediaSourceFactory#setDrmUserAgent`. Use - `MediaSourceFactory#setDrmSessionManagerProvider` instead. - * Remove `MediaSourceFactory#setStreamKeys`. Use - `MediaItem.Builder#setStreamKeys` instead. - * Remove `MediaSourceFactory#createMediaSource(Uri)`. Use - `MediaSourceFactory#createMediaSource(MediaItem)` instead. + * Remove `MediaSourceFactory.setDrmSessionManager`, + `MediaSourceFactory.setDrmHttpDataSourceFactory`, and + `MediaSourceFactory.setDrmUserAgent`. Use + `MediaSourceFactory.setDrmSessionManagerProvider` instead. + * Remove `MediaSourceFactory.setStreamKeys`. Use + `MediaItem.Builder.setStreamKeys` instead. + * Remove `MediaSourceFactory.createMediaSource(Uri)`. Use + `MediaSourceFactory.createMediaSource(MediaItem)` instead. * Remove `setTag` from `DashMediaSource`, `HlsMediaSource` and - `SsMediaSource`. Use `MediaItem.Builder#setTag` instead. - * Remove `DashMediaSource#setLivePresentationDelayMs(long, boolean)`. Use - `MediaItem.Builder#setLiveConfiguration` and - `MediaItem.LiveConfiguration.Builder#setTargetOffsetMs` to override the - manifest, or `DashMediaSource#setFallbackTargetLiveOffsetMs` to provide + `SsMediaSource`. Use `MediaItem.Builder.setTag` instead. + * Remove `DashMediaSource.setLivePresentationDelayMs(long, boolean)`. Use + `MediaItem.Builder.setLiveConfiguration` and + `MediaItem.LiveConfiguration.Builder.setTargetOffsetMs` to override the + manifest, or `DashMediaSource.setFallbackTargetLiveOffsetMs` to provide a fallback value. * Remove `(Simple)ExoPlayer.setThrowsWhenUsingWrongThread`. Opting out of the thread enforcement is no longer possible. * Remove `ActionFile` and `ActionFileUpgradeUtil`. Use ExoPlayer 2.16.1 or before to use `ActionFileUpgradeUtil` to merge legacy action files into `DefaultDownloadIndex`. - * Remove `ProgressiveMediaSource#setExtractorsFactory`. Use + * Remove `ProgressiveMediaSource.setExtractorsFactory`. Use `ProgressiveMediaSource.Factory(DataSource.Factory, ExtractorsFactory)` constructor instead. - * Remove `ProgressiveMediaSource.Factory#setTag` and, and - `ProgressiveMediaSource.Factory#setCustomCacheKey`. Use - `MediaItem.Builder#setTag` and `MediaItem.Builder#setCustomCacheKey` + * Remove `ProgressiveMediaSource.Factory.setTag` and + `ProgressiveMediaSource.Factory.setCustomCacheKey`. Use + `MediaItem.Builder.setTag` and `MediaItem.Builder.setCustomCacheKey` instead. * Remove `DefaultRenderersFactory(Context, @ExtensionRendererMode int)` and `DefaultRenderersFactory(Context, @ExtensionRendererMode int, long)` constructors. Use the `DefaultRenderersFactory(Context)` constructor, - `DefaultRenderersFactory#setExtensionRendererMode`, and - `DefaultRenderersFactory#setAllowedVideoJoiningTimeMs` instead. + `DefaultRenderersFactory.setExtensionRendererMode`, and + `DefaultRenderersFactory.setAllowedVideoJoiningTimeMs` instead. * Remove all public `CronetDataSource` constructors. Use `CronetDataSource.Factory` instead. * Change the following `IntDefs` to `@Target(TYPE_USE)` only. This may break diff --git a/constants.gradle b/constants.gradle index b73f92a9907..ba974fd8e9d 100644 --- a/constants.gradle +++ b/constants.gradle @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. project.ext { - releaseVersion = '1.0.0-alpha02' - releaseVersionCode = 1_000_000_0_02 + releaseVersion = '1.0.0-alpha03' + releaseVersionCode = 1_000_000_0_03 minSdkVersion = 16 appTargetSdkVersion = 29 // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some diff --git a/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt b/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt index 7a910fd8670..f1c1631d451 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt @@ -30,6 +30,7 @@ import android.widget.ListView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.media3.common.MediaItem +import androidx.media3.common.Player import androidx.media3.session.MediaBrowser import androidx.media3.session.SessionToken import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton @@ -179,6 +180,9 @@ class PlayableFolderActivity : AppCompatActivity() { returnConvertView.findViewById(R.id.add_button).setOnClickListener { val browser = this@PlayableFolderActivity.browser ?: return@setOnClickListener browser.addMediaItem(mediaItem) + if (browser.playbackState == Player.STATE_IDLE) { + browser.prepare() + } Snackbar.make( findViewById(R.id.linear_layout), getString(R.string.added_media_item_format, mediaItem.mediaMetadata.title), diff --git a/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt b/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt index 6efe76bccc0..b4ec76ee5a8 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt @@ -96,7 +96,6 @@ class PlaybackService : MediaLibraryService() { val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem() player.setMediaItem(item) - player.prepare() } override fun onSetMediaUri( diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 747bb131733..41d9927a4d4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1e62b660612..b1159fc54f3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Mar 04 12:41:50 GMT 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https://services.gradle.org/distributions/gradle-7.3.3-all.zip diff --git a/gradlew b/gradlew index 91a7e269e19..1b6c787337f 100755 --- a/gradlew +++ b/gradlew @@ -1,79 +1,129 @@ -#!/usr/bin/env bash +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -82,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -90,75 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 8a0b282aa68..ac1b06f9382 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,89 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libraries/common/src/main/java/androidx/media3/common/MediaLibraryInfo.java b/libraries/common/src/main/java/androidx/media3/common/MediaLibraryInfo.java index 84060fae47a..0cf954df4f9 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MediaLibraryInfo.java +++ b/libraries/common/src/main/java/androidx/media3/common/MediaLibraryInfo.java @@ -29,11 +29,11 @@ public final class MediaLibraryInfo { /** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. - public static final String VERSION = "1.0.0-alpha02"; + public static final String VERSION = "1.0.0-alpha03"; /** The version of the library expressed as {@code TAG + "/" + VERSION}. */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha02"; + public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha03"; /** * The version of the library expressed as an integer, for example 1002003300. @@ -47,7 +47,7 @@ public final class MediaLibraryInfo { * (123-045-006-3-00). */ // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. - public static final int VERSION_INT = 1_000_000_0_02; + public static final int VERSION_INT = 1_000_000_0_03; /** Whether the library was compiled with {@link Assertions} checks enabled. */ public static final boolean ASSERTIONS_ENABLED = true; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java index 99896816ae0..84bd8ebbf73 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java @@ -56,6 +56,7 @@ import androidx.media3.exoplayer.analytics.AnalyticsCollector; import androidx.media3.exoplayer.analytics.PlayerId; import androidx.media3.exoplayer.drm.DrmSession; +import androidx.media3.exoplayer.metadata.MetadataRenderer; import androidx.media3.exoplayer.source.BehindLiveWindowException; import androidx.media3.exoplayer.source.MediaPeriod; import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId; @@ -2228,6 +2229,7 @@ private boolean hasReachedServerSideInsertedAdsTransition( return reading.info.isFollowedByTransitionToSameStream && nextPeriod.prepared && (renderer instanceof TextRenderer // [internal: b/181312195] + || renderer instanceof MetadataRenderer || renderer.getReadingPositionUs() >= nextPeriod.getStartPositionRendererTime()); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java index 7186ca79a55..5d755a13364 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java @@ -1739,8 +1739,11 @@ private static Pair getEncodingAndChannelConfigForPassthrough( // the channel count for this encoding, but before then there is no way to query it so we // assume 6 channel audio is supported. if (Util.SDK_INT >= 29) { + // Default to 48 kHz if the format doesn't have a sample rate (for example, for chunkless + // HLS preparation). See [Internal: b/222127949]. + int sampleRate = format.sampleRate != Format.NO_VALUE ? format.sampleRate : 48000; channelCount = - getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, format.sampleRate); + getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, sampleRate); if (channelCount == 0) { Log.w(TAG, "E-AC3 JOC encoding supported but no channel count supported"); return null; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/UnsupportedDrmException.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/UnsupportedDrmException.java index 9b360d2776f..a6741a98916 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/UnsupportedDrmException.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/UnsupportedDrmException.java @@ -24,6 +24,8 @@ import androidx.annotation.IntDef; import androidx.media3.common.util.UnstableApi; import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** Thrown when the requested DRM scheme is not supported. */ @@ -35,8 +37,9 @@ public final class UnsupportedDrmException extends Exception { * #REASON_INSTANTIATION_ERROR}. */ // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility - // with Kotlin usages from before TYPE_USE was added. @Retention(RetentionPolicy.SOURCE) + // with Kotlin usages from before TYPE_USE was added. @Documented + @Retention(RetentionPolicy.SOURCE) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) public @interface Reason {} diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/SingleSampleMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/SingleSampleMediaSource.java index ace3d6971ca..5c0cb861c40 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/SingleSampleMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/SingleSampleMediaSource.java @@ -74,11 +74,12 @@ public Factory setTag(@Nullable Object tag) { } /** - * Sets an optional track id to be used. - * - * @param trackId An optional track id. - * @return This factory, for convenience. + * @deprecated Use {@link MediaItem.SubtitleConfiguration.Builder#setId(String)} instead (on the + * {@link MediaItem.SubtitleConfiguration} passed to {@link + * #createMediaSource(MediaItem.SubtitleConfiguration, long)}). {@code trackId} will only be + * used if {@link MediaItem.SubtitleConfiguration#id} is {@code null}. */ + @Deprecated public Factory setTrackId(@Nullable String trackId) { this.trackId = trackId; return this; @@ -157,29 +158,28 @@ private SingleSampleMediaSource( this.durationUs = durationUs; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; - mediaItem = + this.mediaItem = new MediaItem.Builder() .setUri(Uri.EMPTY) .setMediaId(subtitleConfiguration.uri.toString()) .setSubtitleConfigurations(ImmutableList.of(subtitleConfiguration)) .setTag(tag) .build(); - format = + this.format = new Format.Builder() - .setId(trackId) .setSampleMimeType(firstNonNull(subtitleConfiguration.mimeType, MimeTypes.TEXT_UNKNOWN)) .setLanguage(subtitleConfiguration.language) .setSelectionFlags(subtitleConfiguration.selectionFlags) .setRoleFlags(subtitleConfiguration.roleFlags) .setLabel(subtitleConfiguration.label) - .setId(subtitleConfiguration.id) + .setId(subtitleConfiguration.id != null ? subtitleConfiguration.id : trackId) .build(); - dataSpec = + this.dataSpec = new DataSpec.Builder() .setUri(subtitleConfiguration.uri) .setFlags(DataSpec.FLAG_ALLOW_GZIP) .build(); - timeline = + this.timeline = new SinglePeriodTimeline( durationUs, /* isSeekable= */ true, diff --git a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java index 87499ea2270..5f7dabeff12 100644 --- a/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java +++ b/libraries/exoplayer_ima/src/main/java/androidx/media3/exoplayer/ima/ImaServerSideAdInsertionMediaSource.java @@ -105,11 +105,7 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; -/** - * MediaSource for IMA server side inserted ad streams. - * - *

TODO(bachinger) add code snippet from PlayerActivity - */ +/** MediaSource for IMA server side inserted ad streams. */ @UnstableApi public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSource { @@ -119,8 +115,6 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou * *

Apps can use the {@link ImaServerSideAdInsertionMediaSource.Factory} to customized the * {@link DefaultMediaSourceFactory} that is used to build a player: - * - *

TODO(bachinger) add code snippet from PlayerActivity */ public static final class Factory implements MediaSource.Factory { @@ -461,6 +455,7 @@ private MediaSourceResourceHolder( @Nullable private IOException loadError; private @MonotonicNonNull Timeline contentTimeline; private AdPlaybackState adPlaybackState; + private int firstSeenAdIndexInAdGroup; private ImaServerSideAdInsertionMediaSource( MediaItem mediaItem, @@ -698,18 +693,21 @@ private static AdPlaybackState setVodAdInPlaceholder(Ad ad, AdPlaybackState adPl return adPlaybackState; } - private static AdPlaybackState addLiveAdBreak( + private AdPlaybackState addLiveAdBreak( Ad ad, long currentPeriodPositionUs, AdPlaybackState adPlaybackState) { AdPodInfo adPodInfo = ad.getAdPodInfo(); long adDurationUs = secToUs(ad.getDuration()); int adIndexInAdGroup = adPodInfo.getAdPosition() - 1; - // TODO(b/208398934) Support seeking backwards. if (adIndexInAdGroup == 0 || adPlaybackState.adGroupCount == 1) { + firstSeenAdIndexInAdGroup = adIndexInAdGroup; + // Adjust count and ad index in case we joined the live stream within an ad group. + int adCount = adPodInfo.getTotalAds() - firstSeenAdIndexInAdGroup; + adIndexInAdGroup -= firstSeenAdIndexInAdGroup; // First ad of group. Create a new group with all ads. long[] adDurationsUs = updateAdDurationAndPropagate( - new long[adPodInfo.getTotalAds()], + new long[adCount], adIndexInAdGroup, adDurationUs, secToUs(adPodInfo.getMaxDuration())); @@ -721,6 +719,11 @@ private static AdPlaybackState addLiveAdBreak( /* adDurationsUs...= */ adDurationsUs); } else { int adGroupIndex = adPlaybackState.adGroupCount - 2; + adIndexInAdGroup -= firstSeenAdIndexInAdGroup; + if (adPodInfo.getTotalAds() == adPodInfo.getAdPosition()) { + // Reset the ad index whe we are at the last ad in the group. + firstSeenAdIndexInAdGroup = 0; + } adPlaybackState = updateAdDurationInAdGroup(adGroupIndex, adIndexInAdGroup, adDurationUs, adPlaybackState); AdPlaybackState.AdGroup adGroup = adPlaybackState.getAdGroup(adGroupIndex); @@ -857,7 +860,7 @@ public void onAdEvent(AdEvent event) { long positionInWindowUs = timeline.getPeriod(player.getCurrentPeriodIndex(), new Timeline.Period()) .positionInWindowUs; - long currentPeriodPosition = msToUs(player.getCurrentPosition()) - positionInWindowUs; + long currentPeriodPosition = msToUs(player.getContentPosition()) - positionInWindowUs; newAdPlaybackState = addLiveAdBreak( event.getAd(), diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/FragmentedMp4Extractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/FragmentedMp4Extractor.java index 36284771d16..51f28fde6bf 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/FragmentedMp4Extractor.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/FragmentedMp4Extractor.java @@ -666,14 +666,23 @@ private void onEmsgLeafAtomRead(ParsableByteArray atom) { emsgTrackOutput.sampleData(encodedEventMessage, sampleSize); } - // Output the sample metadata. This is made a little complicated because emsg-v0 atoms - // have presentation time *delta* while v1 atoms have absolute presentation time. + // Output the sample metadata. if (sampleTimeUs == C.TIME_UNSET) { - // We need the first sample timestamp in the segment before we can output the metadata. + // We're processing a v0 emsg atom, which contains a presentation time delta, and cannot yet + // calculate its absolute sample timestamp. Defer outputting the metadata until we can. pendingMetadataSampleInfos.addLast( - new MetadataSampleInfo(presentationTimeDeltaUs, sampleSize)); + new MetadataSampleInfo( + presentationTimeDeltaUs, /* sampleTimeIsRelative= */ true, sampleSize)); + pendingMetadataSampleBytes += sampleSize; + } else if (!pendingMetadataSampleInfos.isEmpty()) { + // We also need to defer outputting metadata if pendingMetadataSampleInfos is non-empty, else + // we will output metadata for samples in the wrong order. See: + // https://github.com/google/ExoPlayer/issues/9996. + pendingMetadataSampleInfos.addLast( + new MetadataSampleInfo(sampleTimeUs, /* sampleTimeIsRelative= */ false, sampleSize)); pendingMetadataSampleBytes += sampleSize; } else { + // We can output the sample metadata immediately. if (timestampAdjuster != null) { sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); } @@ -1459,19 +1468,30 @@ private boolean readSample(ExtractorInput input) throws IOException { return true; } + /** + * Called immediately after outputting a non-metadata sample, to output any pending metadata + * samples. + * + * @param sampleTimeUs The timestamp of the non-metadata sample that was just output. + */ private void outputPendingMetadataSamples(long sampleTimeUs) { while (!pendingMetadataSampleInfos.isEmpty()) { - MetadataSampleInfo sampleInfo = pendingMetadataSampleInfos.removeFirst(); - pendingMetadataSampleBytes -= sampleInfo.size; - long metadataTimeUs = sampleTimeUs + sampleInfo.presentationTimeDeltaUs; + MetadataSampleInfo metadataSampleInfo = pendingMetadataSampleInfos.removeFirst(); + pendingMetadataSampleBytes -= metadataSampleInfo.size; + long metadataSampleTimeUs = metadataSampleInfo.sampleTimeUs; + if (metadataSampleInfo.sampleTimeIsRelative) { + // The metadata sample timestamp is relative to the timestamp of the non-metadata sample + // that was just output. Make it absolute. + metadataSampleTimeUs += sampleTimeUs; + } if (timestampAdjuster != null) { - metadataTimeUs = timestampAdjuster.adjustSampleTimestamp(metadataTimeUs); + metadataSampleTimeUs = timestampAdjuster.adjustSampleTimestamp(metadataSampleTimeUs); } for (TrackOutput emsgTrackOutput : emsgTrackOutputs) { emsgTrackOutput.sampleMetadata( - metadataTimeUs, + metadataSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, - sampleInfo.size, + metadataSampleInfo.size, pendingMetadataSampleBytes, null); } @@ -1577,11 +1597,13 @@ private static boolean shouldParseContainerAtom(int atom) { /** Holds data corresponding to a metadata sample. */ private static final class MetadataSampleInfo { - public final long presentationTimeDeltaUs; + public final long sampleTimeUs; + public final boolean sampleTimeIsRelative; public final int size; - public MetadataSampleInfo(long presentationTimeDeltaUs, int size) { - this.presentationTimeDeltaUs = presentationTimeDeltaUs; + public MetadataSampleInfo(long sampleTimeUs, boolean sampleTimeIsRelative, int size) { + this.sampleTimeUs = sampleTimeUs; + this.sampleTimeIsRelative = sampleTimeIsRelative; this.size = size; } } diff --git a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java b/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java index f2534339058..8c868be1cd8 100644 --- a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java +++ b/libraries/session/src/main/java/androidx/media3/session/DefaultMediaNotificationProvider.java @@ -27,6 +27,7 @@ import androidx.core.app.NotificationCompat; import androidx.core.graphics.drawable.IconCompat; import androidx.media3.common.MediaMetadata; +import androidx.media3.common.Player; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; @@ -93,20 +94,21 @@ public MediaNotification createNotification( IconCompat.createWithResource(context, R.drawable.media3_notification_seek_to_previous), context.getString(R.string.media3_controls_seek_to_previous_description), MediaNotification.ActionFactory.COMMAND_SKIP_TO_PREVIOUS)); - if (mediaController.getPlayWhenReady()) { - // Pause action. - builder.addAction( - actionFactory.createMediaAction( - IconCompat.createWithResource(context, R.drawable.media3_notification_pause), - context.getString(R.string.media3_controls_pause_description), - MediaNotification.ActionFactory.COMMAND_PAUSE)); - } else { + if (mediaController.getPlaybackState() == Player.STATE_ENDED + || !mediaController.getPlayWhenReady()) { // Play action. builder.addAction( actionFactory.createMediaAction( IconCompat.createWithResource(context, R.drawable.media3_notification_play), context.getString(R.string.media3_controls_play_description), MediaNotification.ActionFactory.COMMAND_PLAY)); + } else { + // Pause action. + builder.addAction( + actionFactory.createMediaAction( + IconCompat.createWithResource(context, R.drawable.media3_notification_pause), + context.getString(R.string.media3_controls_pause_description), + MediaNotification.ActionFactory.COMMAND_PAUSE)); } // Skip to next action. builder.addAction( diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java index 8e06166bdd3..b56af6b58f8 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java @@ -198,7 +198,7 @@ public void onLoadChildren( try { int page = options.getInt(EXTRA_PAGE); int pageSize = options.getInt(EXTRA_PAGE_SIZE); - if (page > 0 && pageSize > 0) { + if (page >= 0 && pageSize > 0) { // Requesting the list of children through pagination. @Nullable LibraryParams params = @@ -223,7 +223,7 @@ public void onLoadChildren( parentId, /* page= */ 0, /* pageSize= */ Integer.MAX_VALUE, - /* extras= */ null); + /* params= */ null); sendLibraryResultWithMediaItemsWhenReady(result, future); }); } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java b/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java index c89ebe092d0..7d3ceac3bb5 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaNotificationManager.java @@ -196,7 +196,9 @@ public void onConnected() { @Override public void onEvents(Player player, Player.Events events) { if (events.containsAny( - Player.EVENT_PLAY_WHEN_READY_CHANGED, Player.EVENT_MEDIA_METADATA_CHANGED)) { + Player.EVENT_PLAYBACK_STATE_CHANGED, + Player.EVENT_PLAY_WHEN_READY_CHANGED, + Player.EVENT_MEDIA_METADATA_CHANGED)) { updateNotification(session); } } diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java index 2aef615164e..41219049558 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionLegacyStub.java @@ -28,6 +28,8 @@ import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE; import static androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH; import static androidx.media3.common.Player.COMMAND_STOP; +import static androidx.media3.common.Player.STATE_ENDED; +import static androidx.media3.common.Player.STATE_IDLE; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.common.util.Util.postOrRun; @@ -231,7 +233,17 @@ private void handleMediaPlayPauseOnHandler(RemoteUserInfo remoteUserInfo) { } else { dispatchSessionTaskWithPlayerCommand( COMMAND_PLAY_PAUSE, - (controller) -> sessionImpl.getPlayerWrapper().play(), + (controller) -> { + PlayerWrapper playerWrapper = sessionImpl.getPlayerWrapper(); + @Player.State int playbackState = playerWrapper.getPlaybackState(); + if (playbackState == STATE_IDLE) { + playerWrapper.prepare(); + } else if (playbackState == STATE_ENDED) { + playerWrapper.seekTo( + playerWrapper.getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET); + } + playerWrapper.play(); + }, remoteUserInfo); } } @@ -285,7 +297,17 @@ public void onPrepareFromUri(Uri mediaUri, @Nullable Bundle extras) { public void onPlay() { dispatchSessionTaskWithPlayerCommand( COMMAND_PLAY_PAUSE, - controller -> sessionImpl.getPlayerWrapper().play(), + controller -> { + PlayerWrapper playerWrapper = sessionImpl.getPlayerWrapper(); + @Player.State int playbackState = playerWrapper.getPlaybackState(); + if (playbackState == Player.STATE_IDLE) { + playerWrapper.prepare(); + } else if (playbackState == Player.STATE_ENDED) { + playerWrapper.seekTo( + playerWrapper.getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET); + } + playerWrapper.play(); + }, sessionCompat.getCurrentControllerInfo()); } @@ -321,7 +343,15 @@ public void onPlayFromUri(Uri mediaUri, @Nullable Bundle extras) { if (sessionImpl.onSetMediaUriOnHandler( controller, mediaUri, extras == null ? Bundle.EMPTY : extras) == RESULT_SUCCESS) { - sessionImpl.getPlayerWrapper().play(); + PlayerWrapper playerWrapper = sessionImpl.getPlayerWrapper(); + @Player.State int playbackState = playerWrapper.getPlaybackState(); + if (playbackState == Player.STATE_IDLE) { + playerWrapper.prepare(); + } else if (playbackState == STATE_ENDED) { + playerWrapper.seekTo( + playerWrapper.getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET); + } + playerWrapper.play(); } }); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionAndControllerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionAndControllerTest.java index 0ff9c10b0a0..a31ea620392 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionAndControllerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionAndControllerTest.java @@ -181,7 +181,6 @@ public void play_withTheSameLooper_sendsToSession() throws Exception { MockPlayer player = new MockPlayer.Builder() .setApplicationLooper(threadTestRule.getHandler().getLooper()) - .setLatchCount(1) .build(); MediaSession session = sessionTestRule.ensureReleaseAfterTest( @@ -190,8 +189,7 @@ public void play_withTheSameLooper_sendsToSession() throws Exception { threadTestRule.getHandler().postAndSync(controller::play); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java index 8f8d6e6c918..4eeb55c75ca 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackTest.java @@ -74,7 +74,6 @@ public void setUp() { context = ApplicationProvider.getApplicationContext(); player = new MockPlayer.Builder() - .setLatchCount(1) .setApplicationLooper(threadTestRule.getHandler().getLooper()) .build(); } @@ -157,15 +156,14 @@ public int onPlayerCommandRequest( controllerTestRule.createRemoteController(session.getToken()); controller.prepare(); - assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse(); - assertThat(player.prepareCalled).isFalse(); + Thread.sleep(NO_RESPONSE_TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse(); assertThat(commands).hasSize(1); assertThat(commands.get(0)).isEqualTo(Player.COMMAND_PREPARE); controller.play(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); - assertThat(player.prepareCalled).isFalse(); + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isFalse(); assertThat(commands).hasSize(2); assertThat(commands.get(1)).isEqualTo(Player.COMMAND_PLAY_PAUSE); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java index f14252eb464..d3288697266 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionCallbackWithMediaControllerCompatTest.java @@ -94,8 +94,7 @@ public class MediaSessionCallbackWithMediaControllerCompatTest { public void setUp() { context = ApplicationProvider.getApplicationContext(); handler = threadTestRule.getHandler(); - player = - new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build(); + player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build(); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); } @@ -206,8 +205,8 @@ public void play() throws Exception { context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); controller.getTransportControls().play(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test @@ -222,8 +221,8 @@ public void pause() throws Exception { context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); controller.getTransportControls().pause(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.pauseCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS); } @Test @@ -238,8 +237,8 @@ public void stop() throws Exception { context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); controller.getTransportControls().stop(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.stopCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS); } @Test @@ -254,8 +253,8 @@ public void prepare() throws Exception { context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); controller.getTransportControls().prepare(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.prepareCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS); } @Test @@ -271,8 +270,8 @@ public void seekTo() throws Exception { long seekPosition = 12125L; controller.getTransportControls().seekTo(seekPosition); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO, TIMEOUT_MS); assertThat(player.seekPositionMs).isEqualTo(seekPosition); } @@ -289,8 +288,8 @@ public void setPlaybackSpeed_callsSetPlaybackSpeed() throws Exception { float testSpeed = 2.0f; controller.getTransportControls().setPlaybackSpeed(testSpeed); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setPlaybackSpeedCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_PLAYBACK_SPEED, TIMEOUT_MS); assertThat(player.playbackParameters.speed).isEqualTo(testSpeed); } @@ -316,8 +315,7 @@ public void addQueueItem() throws Exception { MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder().setMediaId(mediaId).build(); controller.addQueueItem(desc); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.addMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.mediaItem.mediaId).isEqualTo(mediaId); } @@ -344,8 +342,7 @@ public void addQueueItemWithIndex() throws Exception { MediaDescriptionCompat desc = new MediaDescriptionCompat.Builder().setMediaId(mediaId).build(); controller.addQueueItem(desc, testIndex); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.addMediaItemWithIndexCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEM_WITH_INDEX, TIMEOUT_MS); assertThat(player.index).isEqualTo(testIndex); assertThat(player.mediaItem.mediaId).isEqualTo(mediaId); } @@ -375,8 +372,7 @@ public void removeQueueItem() throws Exception { new MediaDescriptionCompat.Builder().setMediaId(targetItem.mediaId).build(); controller.removeQueueItem(desc); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.removeMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_REMOVE_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.index).isEqualTo(targetIndex); } @@ -392,8 +388,8 @@ public void skipToPrevious() throws Exception { context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); controller.getTransportControls().skipToPrevious(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToPreviousCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS); } @Test @@ -408,8 +404,8 @@ public void skipToNext() throws Exception { context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); controller.getTransportControls().skipToNext(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToNextCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS); } @Test @@ -434,8 +430,8 @@ public void skipToQueueItem() throws Exception { int targetIndex = 3; controller.getTransportControls().skipToQueueItem(queue.get(targetIndex).getQueueId()); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToDefaultPositionWithMediaItemIndexCalled).isTrue(); + player.awaitMethodCalled( + MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION_WITH_MEDIA_ITEM_INDEX, TIMEOUT_MS); assertThat(player.seekMediaItemIndex).isEqualTo(targetIndex); } @@ -452,9 +448,8 @@ public void setShuffleMode() throws Exception { @PlaybackStateCompat.ShuffleMode int testShuffleMode = PlaybackStateCompat.SHUFFLE_MODE_GROUP; controller.getTransportControls().setShuffleMode(testShuffleMode); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setShuffleModeCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_SHUFFLE_MODE, TIMEOUT_MS); assertThat(player.shuffleModeEnabled).isTrue(); } @@ -471,9 +466,8 @@ public void setRepeatMode() throws Exception { int testRepeatMode = Player.REPEAT_MODE_ALL; controller.getTransportControls().setRepeatMode(testRepeatMode); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setRepeatModeCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_REPEAT_MODE, TIMEOUT_MS); assertThat(player.repeatMode).isEqualTo(testRepeatMode); } @@ -488,7 +482,7 @@ public void setVolumeTo_setsDeviceVolume() throws Exception { new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); MockPlayer remotePlayer = - new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build(); + new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build(); handler.postAndSync( () -> { remotePlayer.deviceInfo = @@ -501,8 +495,7 @@ public void setVolumeTo_setsDeviceVolume() throws Exception { int targetVolume = 50; controller.setVolumeTo(targetVolume, /* flags= */ 0); - assertThat(remotePlayer.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(remotePlayer.setDeviceVolumeCalled).isTrue(); + remotePlayer.awaitMethodCalled(MockPlayer.METHOD_SET_DEVICE_VOLUME, TIMEOUT_MS); assertThat(remotePlayer.deviceVolume).isEqualTo(targetVolume); } @@ -517,7 +510,7 @@ public void adjustVolume_raise_increasesDeviceVolume() throws Exception { new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); MockPlayer remotePlayer = - new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build(); + new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build(); handler.postAndSync( () -> { remotePlayer.deviceInfo = @@ -529,8 +522,7 @@ public void adjustVolume_raise_increasesDeviceVolume() throws Exception { controller.adjustVolume(AudioManager.ADJUST_RAISE, /* flags= */ 0); - assertThat(remotePlayer.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(remotePlayer.increaseDeviceVolumeCalled).isTrue(); + remotePlayer.awaitMethodCalled(MockPlayer.METHOD_INCREASE_DEVICE_VOLUME, TIMEOUT_MS); } @Test @@ -544,7 +536,7 @@ public void adjustVolume_lower_decreasesDeviceVolume() throws Exception { new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); MockPlayer remotePlayer = - new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build(); + new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build(); handler.postAndSync( () -> { remotePlayer.deviceInfo = @@ -556,8 +548,7 @@ public void adjustVolume_lower_decreasesDeviceVolume() throws Exception { controller.adjustVolume(AudioManager.ADJUST_LOWER, /* flags= */ 0); - assertThat(remotePlayer.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(remotePlayer.decreaseDeviceVolumeCalled).isTrue(); + remotePlayer.awaitMethodCalled(MockPlayer.METHOD_DECREASE_DEVICE_VOLUME, TIMEOUT_MS); } @Test @@ -704,7 +695,9 @@ public ListenableFuture onCustomCommand( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.sendCommand(testCommand, testArgs, /* cb= */ null); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); } @@ -723,13 +716,15 @@ public MediaSession.ConnectionResult onConnect( .setId("controllerCallback_sessionRejects") .setSessionCallback(sessionCallback) .build(); - // Session will not accept the controller's commands. controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().play(); - assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse(); + + Thread.sleep(NO_RESPONSE_TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse(); } @Test @@ -757,10 +752,11 @@ public int onSetMediaUri( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().prepareFromUri(mediaUri, bundle); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.prepareCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS); } @Test @@ -788,10 +784,11 @@ public int onSetMediaUri( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().playFromUri(request, bundle); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test @@ -820,10 +817,11 @@ public int onSetMediaUri( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().prepareFromMediaId(request, bundle); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.prepareCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS); } @Test @@ -852,10 +850,11 @@ public int onSetMediaUri( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().playFromMediaId(mediaId, bundle); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test @@ -884,10 +883,11 @@ public int onSetMediaUri( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().prepareFromSearch(query, bundle); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.prepareCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS); } @Test @@ -916,10 +916,11 @@ public int onSetMediaUri( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().playFromSearch(query, bundle); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test @@ -944,7 +945,6 @@ public ListenableFuture onSetRating( return Futures.immediateFuture(new SessionResult(RESULT_SUCCESS)); } }; - handler.postAndSync( () -> { List mediaItems = MediaTestUtils.createMediaItems(mediaId); @@ -958,7 +958,9 @@ public ListenableFuture onSetRating( controller = new RemoteMediaControllerCompat( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); + controller.getTransportControls().setRating(rating); + assertThat(latch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); } @@ -991,16 +993,17 @@ public int onPlayerCommandRequest( context, session.getSessionCompat().getSessionToken(), /* waitForConnection= */ true); controller.getTransportControls().pause(); + assertThat(latchForPause.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse(); - assertThat(player.pauseCalled).isFalse(); + Thread.sleep(NO_RESPONSE_TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PAUSE)).isFalse(); assertThat(commands).hasSize(1); assertThat(commands.get(0)).isEqualTo(COMMAND_PLAY_PAUSE); controller.getTransportControls().prepare(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.prepareCalled).isTrue(); - assertThat(player.pauseCalled).isFalse(); + + player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PAUSE)).isFalse(); assertThat(commands).hasSize(2); assertThat(commands.get(1)).isEqualTo(COMMAND_PREPARE); } @@ -1056,7 +1059,8 @@ public void closedSession_ignoresController() throws Exception { session = null; controller.getTransportControls().play(); - assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse(); + Thread.sleep(NO_RESPONSE_TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse(); // Ensure that the controller cannot use newly create session with the same ID. // Recreated session has different session stub, so previously created controller @@ -1068,7 +1072,8 @@ public void closedSession_ignoresController() throws Exception { .build(); controller.getTransportControls().play(); - assertThat(player.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse(); + Thread.sleep(NO_RESPONSE_TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse(); } private static class TestSessionCallback implements SessionCallback { diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java index 22b3e8a3f5d..b1b14a3aa5e 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionKeyEventTest.java @@ -89,8 +89,7 @@ public void setUp() throws Exception { Context context = ApplicationProvider.getApplicationContext(); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); handler = threadTestRule.getHandler(); - player = - new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build(); + player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build(); sessionCallback = new TestSessionCallback(); session = new MediaSession.Builder(context, player).setSessionCallback(sessionCallback).build(); @@ -120,7 +119,7 @@ public void setUp() throws Exception { } @After - public void cleanUp() throws Exception { + public void tearDown() throws Exception { handler.postAndSync( () -> { if (mediaPlayer != null) { @@ -131,55 +130,46 @@ public void cleanUp() throws Exception { session.release(); } - private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) { - audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); - audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); - if (doubleTap) { - audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); - audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); - } - } - @Test public void playKeyEvent() throws Exception { dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY, false); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test public void pauseKeyEvent() throws Exception { dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PAUSE, false); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.pauseCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS); } @Test public void nextKeyEvent() throws Exception { dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, false); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToNextCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS); } @Test public void previousKeyEvent() throws Exception { dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS, false); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToPreviousCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS); } @Test public void stopKeyEvent() throws Exception { dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, false); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.stopCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS); } @Test public void playPauseKeyEvent_play() throws Exception { dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test @@ -188,18 +178,28 @@ public void playPauseKeyEvent_pause() throws Exception { () -> { player.playWhenReady = true; }); + dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, false); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.pauseCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS); } @Test public void playPauseKeyEvent_doubleTapIsTranslatedToSkipToNext() throws Exception { dispatchMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, true); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToNextCalled).isTrue(); - assertThat(player.playCalled).isFalse(); - assertThat(player.pauseCalled).isFalse(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isFalse(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PAUSE)).isFalse(); + } + + private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) { + audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); + audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + if (doubleTap) { + audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); + audioManager.dispatchMediaKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + } } private static class TestSessionCallback implements MediaSession.SessionCallback { diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPermissionTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPermissionTest.java index 2213f0700c2..f0f129a49ff 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPermissionTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPermissionTest.java @@ -88,7 +88,7 @@ public void setUp() { } @After - public void cleanUp() { + public void tearDown() { if (session != null) { session.release(); session = null; @@ -97,55 +97,6 @@ public void cleanUp() { callback = null; } - private void createSessionWithAvailableCommands( - SessionCommands sessionCommands, Player.Commands playerCommands) { - player = - new MockPlayer.Builder() - .setLatchCount(1) - .setApplicationLooper(threadTestRule.getHandler().getLooper()) - .build(); - callback = - new MySessionCallback() { - @Override - public MediaSession.ConnectionResult onConnect( - MediaSession session, ControllerInfo controller) { - if (!TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())) { - return MediaSession.ConnectionResult.reject(); - } - return MediaSession.ConnectionResult.accept(sessionCommands, playerCommands); - } - }; - if (this.session != null) { - this.session.release(); - } - this.session = - new MediaSession.Builder(context, player) - .setId(SESSION_ID) - .setSessionCallback(callback) - .build(); - } - - private SessionCommands createSessionCommandsWith(SessionCommand command) { - return new SessionCommands.Builder().add(command).build(); - } - - private void testOnCommandRequest(int commandCode, PermissionTestTask runnable) throws Exception { - createSessionWithAvailableCommands( - SessionCommands.EMPTY, createPlayerCommandsWith(commandCode)); - runnable.run(controllerTestRule.createRemoteController(session.getToken())); - - assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(callback.onCommandRequestCalled).isTrue(); - assertThat(callback.command).isEqualTo(commandCode); - - createSessionWithAvailableCommands( - SessionCommands.EMPTY, createPlayerCommandsWithout(commandCode)); - runnable.run(controllerTestRule.createRemoteController(session.getToken())); - - assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse(); - assertThat(callback.onCommandRequestCalled).isFalse(); - } - @Test public void play() throws Exception { testOnCommandRequest(COMMAND_PLAY_PAUSE, RemoteMediaController::play); @@ -409,4 +360,52 @@ public ListenableFuture onSetRating( return Futures.immediateFuture(new SessionResult(RESULT_SUCCESS)); } } + + private void createSessionWithAvailableCommands( + SessionCommands sessionCommands, Player.Commands playerCommands) { + player = + new MockPlayer.Builder() + .setApplicationLooper(threadTestRule.getHandler().getLooper()) + .build(); + callback = + new MySessionCallback() { + @Override + public MediaSession.ConnectionResult onConnect( + MediaSession session, ControllerInfo controller) { + if (!TextUtils.equals(SUPPORT_APP_PACKAGE_NAME, controller.getPackageName())) { + return MediaSession.ConnectionResult.reject(); + } + return MediaSession.ConnectionResult.accept(sessionCommands, playerCommands); + } + }; + if (this.session != null) { + this.session.release(); + } + this.session = + new MediaSession.Builder(context, player) + .setId(SESSION_ID) + .setSessionCallback(callback) + .build(); + } + + private SessionCommands createSessionCommandsWith(SessionCommand command) { + return new SessionCommands.Builder().add(command).build(); + } + + private void testOnCommandRequest(int commandCode, PermissionTestTask runnable) throws Exception { + createSessionWithAvailableCommands( + SessionCommands.EMPTY, createPlayerCommandsWith(commandCode)); + runnable.run(controllerTestRule.createRemoteController(session.getToken())); + + assertThat(callback.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + assertThat(callback.onCommandRequestCalled).isTrue(); + assertThat(callback.command).isEqualTo(commandCode); + + createSessionWithAvailableCommands( + SessionCommands.EMPTY, createPlayerCommandsWithout(commandCode)); + runnable.run(controllerTestRule.createRemoteController(session.getToken())); + + assertThat(callback.countDownLatch.await(NO_RESPONSE_TIMEOUT_MS, MILLISECONDS)).isFalse(); + assertThat(callback.onCommandRequestCalled).isFalse(); + } } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java index 68dea9d90de..442a84f3c25 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionPlayerTest.java @@ -16,10 +16,8 @@ package androidx.media3.session; import static androidx.media3.test.session.common.CommonConstants.SUPPORT_APP_PACKAGE_NAME; -import static androidx.media3.test.session.common.TestUtils.LONG_TIMEOUT_MS; import static androidx.media3.test.session.common.TestUtils.TIMEOUT_MS; import static com.google.common.truth.Truth.assertThat; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import androidx.media3.common.DeviceInfo; import androidx.media3.common.MediaItem; @@ -63,7 +61,6 @@ public class MediaSessionPlayerTest { public void setUp() throws Exception { player = new MockPlayer.Builder() - .setLatchCount(1) .setApplicationLooper(threadTestRule.getHandler().getLooper()) .setMediaItems(/* itemCount= */ 5) .build(); @@ -87,62 +84,64 @@ public MediaSession.ConnectionResult onConnect( } @After - public void cleanUp() { - if (session != null) { - session.release(); - } + public void tearDown() throws Exception { + controller.release(); + session.release(); } @Test public void play() throws Exception { controller.play(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.playCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PLAY, TIMEOUT_MS); } @Test public void pause() throws Exception { controller.pause(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.pauseCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PAUSE, TIMEOUT_MS); } @Test public void prepare() throws Exception { controller.prepare(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.prepareCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_PREPARE, TIMEOUT_MS); } @Test public void stop() throws Exception { controller.stop(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.stopCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_STOP, TIMEOUT_MS); } @Test public void setPlayWhenReady() throws Exception { boolean testPlayWhenReady = true; + controller.setPlayWhenReady(testPlayWhenReady); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setPlayWhenReadyCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_PLAY_WHEN_READY, TIMEOUT_MS); assertThat(player.playWhenReady).isEqualTo(testPlayWhenReady); } @Test public void seekToDefaultPosition() throws Exception { controller.seekToDefaultPosition(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToDefaultPositionCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION, TIMEOUT_MS); } @Test public void seekToDefaultPosition_withMediaItemIndex() throws Exception { int mediaItemIndex = 3; + controller.seekToDefaultPosition(mediaItemIndex); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToDefaultPositionWithMediaItemIndexCalled).isTrue(); + + player.awaitMethodCalled( + MockPlayer.METHOD_SEEK_TO_DEFAULT_POSITION_WITH_MEDIA_ITEM_INDEX, TIMEOUT_MS); assertThat(player.seekMediaItemIndex).isEqualTo(mediaItemIndex); } @@ -150,8 +149,8 @@ public void seekToDefaultPosition_withMediaItemIndex() throws Exception { public void seekTo() throws Exception { long seekPositionMs = 12125L; controller.seekTo(seekPositionMs); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO, TIMEOUT_MS); assertThat(player.seekPositionMs).isEqualTo(seekPositionMs); } @@ -159,9 +158,10 @@ public void seekTo() throws Exception { public void seekTo_withMediaItemIndex() throws Exception { int mediaItemIndex = 3; long seekPositionMs = 12125L; + controller.seekTo(mediaItemIndex, seekPositionMs); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToWithMediaItemIndexCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX, TIMEOUT_MS); assertThat(player.seekMediaItemIndex).isEqualTo(mediaItemIndex); assertThat(player.seekPositionMs).isEqualTo(seekPositionMs); } @@ -169,8 +169,10 @@ public void seekTo_withMediaItemIndex() throws Exception { @Test public void setPlaybackSpeed() throws Exception { float testSpeed = 1.5f; + controller.setPlaybackSpeed(testSpeed); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_PLAYBACK_SPEED, TIMEOUT_MS); assertThat(player.playbackParameters.speed).isEqualTo(testSpeed); } @@ -178,9 +180,10 @@ public void setPlaybackSpeed() throws Exception { public void setPlaybackParameters() throws Exception { PlaybackParameters testPlaybackParameters = new PlaybackParameters(/* speed= */ 1.4f, /* pitch= */ 2.3f); + controller.setPlaybackParameters(testPlaybackParameters); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setPlaybackParametersCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_PLAYBACK_PARAMETERS, TIMEOUT_MS); assertThat(player.playbackParameters).isEqualTo(testPlaybackParameters); } @@ -194,8 +197,7 @@ public void setMediaItem() throws Exception { controller.setMediaItem(item); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.mediaItem).isEqualTo(item); assertThat(player.startPositionMs).isEqualTo(startPositionMs); assertThat(player.resetPosition).isEqualTo(resetPosition); @@ -211,8 +213,7 @@ public void setMediaItem_withStartPosition() throws Exception { controller.setMediaItem(item); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.mediaItem).isEqualTo(item); assertThat(player.startPositionMs).isEqualTo(startPositionMs); assertThat(player.resetPosition).isEqualTo(resetPosition); @@ -228,8 +229,7 @@ public void setMediaItem_withResetPosition() throws Exception { controller.setMediaItem(item); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.mediaItem).isEqualTo(item); assertThat(player.startPositionMs).isEqualTo(startPositionMs); assertThat(player.resetPosition).isEqualTo(resetPosition); @@ -241,8 +241,7 @@ public void setMediaItems() throws Exception { controller.setMediaItems(items); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemsCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS); assertThat(player.mediaItems).isEqualTo(items); assertThat(player.resetPosition).isFalse(); } @@ -253,8 +252,7 @@ public void setMediaItems_withResetPosition() throws Exception { controller.setMediaItems(items, /* resetPosition= */ true); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemsWithResetPositionCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, TIMEOUT_MS); assertThat(player.mediaItems).isEqualTo(items); assertThat(player.resetPosition).isTrue(); } @@ -267,8 +265,7 @@ public void setMediaItems_withStartMediaItemIndex() throws Exception { controller.setMediaItems(items, startMediaItemIndex, startPositionMs); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemsWithStartIndexCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, TIMEOUT_MS); assertThat(player.mediaItems).isEqualTo(items); assertThat(player.startMediaItemIndex).isEqualTo(startMediaItemIndex); assertThat(player.startPositionMs).isEqualTo(startPositionMs); @@ -279,9 +276,10 @@ public void setMediaItems_withDuplicatedItems() throws Exception { int listSize = 4; List list = MediaTestUtils.createMediaItems(listSize); list.set(2, list.get(1)); + controller.setMediaItems(list); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemsCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS); assertThat(player.mediaItems.size()).isEqualTo(listSize); for (int i = 0; i < listSize; i++) { assertThat(player.mediaItems.get(i).mediaId).isEqualTo(list.get(i).mediaId); @@ -293,9 +291,8 @@ public void setMediaItems_withLongPlaylist() throws Exception { int listSize = 5000; // Make client app to generate a long list, and call setMediaItems() with it. controller.createAndSetFakeMediaItems(listSize); - assertThat(player.countDownLatch.await(LONG_TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setMediaItemsCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS, TIMEOUT_MS); assertThat(player.mediaItems).isNotNull(); assertThat(player.mediaItems.size()).isEqualTo(listSize); for (int i = 0; i < listSize; i++) { @@ -310,8 +307,7 @@ public void setPlaylistMetadata() throws Exception { controller.setPlaylistMetadata(playlistMetadata); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setPlaylistMetadataCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_PLAYLIST_METADATA, TIMEOUT_MS); assertThat(player.playlistMetadata).isEqualTo(playlistMetadata); } @@ -321,8 +317,7 @@ public void addMediaItem() throws Exception { controller.addMediaItem(mediaItem); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.addMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.mediaItem).isEqualTo(mediaItem); } @@ -333,8 +328,7 @@ public void addMediaItem_withIndex() throws Exception { controller.addMediaItem(index, mediaItem); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.addMediaItemWithIndexCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEM_WITH_INDEX, TIMEOUT_MS); assertThat(player.index).isEqualTo(index); assertThat(player.mediaItem).isEqualTo(mediaItem); } @@ -346,8 +340,7 @@ public void addMediaItems() throws Exception { controller.addMediaItems(mediaItems); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.addMediaItemsCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS, TIMEOUT_MS); assertThat(player.mediaItems).isEqualTo(mediaItems); } @@ -359,8 +352,7 @@ public void addMediaItems_withIndex() throws Exception { controller.addMediaItems(index, mediaItems); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.addMediaItemsWithIndexCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS_WITH_INDEX, TIMEOUT_MS); assertThat(player.index).isEqualTo(index); assertThat(player.mediaItems).isEqualTo(mediaItems); } @@ -371,8 +363,7 @@ public void removeMediaItem() throws Exception { controller.removeMediaItem(index); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.removeMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_REMOVE_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.index).isEqualTo(index); } @@ -383,8 +374,7 @@ public void removeMediaItems() throws Exception { controller.removeMediaItems(fromIndex, toIndex); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.removeMediaItemsCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_REMOVE_MEDIA_ITEMS, TIMEOUT_MS); assertThat(player.fromIndex).isEqualTo(fromIndex); assertThat(player.toIndex).isEqualTo(toIndex); } @@ -393,8 +383,7 @@ public void removeMediaItems() throws Exception { public void clearMediaItems() throws Exception { controller.clearMediaItems(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.clearMediaItemsCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_CLEAR_MEDIA_ITEMS, TIMEOUT_MS); } @Test @@ -404,8 +393,7 @@ public void moveMediaItem() throws Exception { controller.moveMediaItem(index, newIndex); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.moveMediaItemCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_MOVE_MEDIA_ITEM, TIMEOUT_MS); assertThat(player.index).isEqualTo(index); assertThat(player.newIndex).isEqualTo(newIndex); } @@ -418,8 +406,7 @@ public void moveMediaItems() throws Exception { controller.moveMediaItems(fromIndex, toIndex, newIndex); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.moveMediaItemsCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_MOVE_MEDIA_ITEMS, TIMEOUT_MS); assertThat(player.fromIndex).isEqualTo(fromIndex); assertThat(player.toIndex).isEqualTo(toIndex); assertThat(player.newIndex).isEqualTo(newIndex); @@ -428,68 +415,69 @@ public void moveMediaItems() throws Exception { @Test public void seekToPreviousMediaItem() throws Exception { controller.seekToPreviousMediaItem(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToPreviousMediaItemCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM, TIMEOUT_MS); } @Test public void seekToNextMediaItem() throws Exception { controller.seekToNextMediaItem(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToNextMediaItemCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT_MEDIA_ITEM, TIMEOUT_MS); } @Test public void seekToPrevious() throws Exception { controller.seekToPrevious(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToPreviousCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS, TIMEOUT_MS); } @Test public void seekToNext() throws Exception { controller.seekToNext(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToNextCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO_NEXT, TIMEOUT_MS); } @Test public void setShuffleModeEnabled() throws Exception { boolean testShuffleModeEnabled = true; + controller.setShuffleModeEnabled(testShuffleModeEnabled); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setShuffleModeCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_SHUFFLE_MODE, TIMEOUT_MS); assertThat(player.shuffleModeEnabled).isEqualTo(testShuffleModeEnabled); } @Test public void setRepeatMode() throws Exception { int testRepeatMode = Player.REPEAT_MODE_ALL; + controller.setRepeatMode(testRepeatMode); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setRepeatModeCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_REPEAT_MODE, TIMEOUT_MS); assertThat(player.repeatMode).isEqualTo(testRepeatMode); } @Test public void setVolume() throws Exception { float testVolume = .123f; + controller.setVolume(testVolume); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setVolumeCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_VOLUME, TIMEOUT_MS); assertThat(player.volume).isEqualTo(testVolume); } @Test public void setDeviceVolume() throws Exception { changePlaybackTypeToRemote(); - int testVolume = 12; + controller.setDeviceVolume(testVolume); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setDeviceVolumeCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_DEVICE_VOLUME, TIMEOUT_MS); assertThat(player.deviceVolume).isEqualTo(testVolume); } @@ -499,8 +487,7 @@ public void increaseDeviceVolume() throws Exception { controller.increaseDeviceVolume(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.increaseDeviceVolumeCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_INCREASE_DEVICE_VOLUME, TIMEOUT_MS); } @Test @@ -509,16 +496,16 @@ public void decreaseDeviceVolume() throws Exception { controller.decreaseDeviceVolume(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.decreaseDeviceVolumeCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_DECREASE_DEVICE_VOLUME, TIMEOUT_MS); } @Test public void setDeviceMuted() throws Exception { player.deviceMuted = false; + controller.setDeviceMuted(true); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setDeviceMutedCalled).isTrue(); + + player.awaitMethodCalled(MockPlayer.METHOD_SET_DEVICE_MUTED, TIMEOUT_MS); assertThat(player.deviceMuted).isTrue(); } @@ -526,16 +513,14 @@ public void setDeviceMuted() throws Exception { public void seekBack() throws Exception { controller.seekBack(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekBackCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_BACK, TIMEOUT_MS); } @Test public void seekForward() throws Exception { controller.seekForward(); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekForwardCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_FORWARD, TIMEOUT_MS); } @Test @@ -545,8 +530,7 @@ public void setTrackSelectionParameters() throws Exception { controller.setTrackSelectionParameters(trackSelectionParameters); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.setTrackSelectionParametersCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SET_TRACK_SELECTION_PARAMETERS, TIMEOUT_MS); assertThat(player.trackSelectionParameters).isEqualTo(trackSelectionParameters); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionTest.java index 1460c42d6bd..e79f809572d 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MediaSessionTest.java @@ -47,6 +47,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.After; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; @@ -78,8 +79,7 @@ public class MediaSessionTest { public void setUp() throws Exception { context = ApplicationProvider.getApplicationContext(); handler = threadTestRule.getHandler(); - player = - new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build(); + player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build(); session = sessionTestRule.ensureReleaseAfterTest( @@ -107,6 +107,16 @@ public MediaSession.ConnectionResult onConnect( .get(TIMEOUT_MS, MILLISECONDS); } + @After + public void tearDown() throws Exception { + if ((controller != null)) { + threadTestRule.getHandler().postAndSync(() -> controller.release()); + } + if (session != null) { + session.release(); + } + } + @Test public void builder() { MediaSession.Builder builder; @@ -394,8 +404,7 @@ public void onSessionReady() { long testSeekPositionMs = 1234; controllerCompat.getTransportControls().seekTo(testSeekPositionMs); - assertThat(player.countDownLatch.await(TIMEOUT_MS, MILLISECONDS)).isTrue(); - assertThat(player.seekToCalled).isTrue(); + player.awaitMethodCalled(MockPlayer.METHOD_SEEK_TO, TIMEOUT_MS); assertThat(player.seekPositionMs).isEqualTo(testSeekPositionMs); } diff --git a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MockPlayerTest.java b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MockPlayerTest.java index 8fc8b456ed3..f97a659fe34 100644 --- a/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MockPlayerTest.java +++ b/libraries/test_session_current/src/androidTest/java/androidx/media3/session/MockPlayerTest.java @@ -44,81 +44,98 @@ public void setUp() { @Test public void play() { player.play(); - assertThat(player.playCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PLAY)).isTrue(); } @Test public void pause() { player.pause(); - assertThat(player.pauseCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PAUSE)).isTrue(); } @Test public void prepare() { player.prepare(); - assertThat(player.prepareCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_PREPARE)).isTrue(); } @Test public void stop() { player.stop(); - assertThat(player.stopCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_STOP)).isTrue(); } @Test public void release() { player.release(); - assertThat(player.releaseCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_RELEASE)).isTrue(); } @Test public void setPlayWhenReady() { boolean testPlayWhenReady = false; + player.setPlayWhenReady(testPlayWhenReady); - assertThat(player.setPlayWhenReadyCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_PLAY_WHEN_READY)).isTrue(); } @Test public void seekTo() { long pos = 1004L; + player.seekTo(pos); - assertThat(player.seekToCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO)).isTrue(); assertThat(player.seekPositionMs).isEqualTo(pos); } @Test public void seekBack() { player.seekBack(); - assertThat(player.seekBackCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_BACK)).isTrue(); } @Test public void seekForward() { player.seekForward(); - assertThat(player.seekForwardCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_FORWARD)).isTrue(); } @Test public void setPlaybackParameters() { PlaybackParameters playbackParameters = new PlaybackParameters(/* speed= */ 1.5f); + player.setPlaybackParameters(playbackParameters); - assertThat(player.setPlaybackParametersCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_PLAYBACK_PARAMETERS)).isTrue(); assertThat(player.playbackParameters).isEqualTo(playbackParameters); } @Test public void setPlaybackSpeed() { float speed = 1.5f; + player.setPlaybackSpeed(speed); - assertThat(player.setPlaybackSpeedCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_PLAYBACK_SPEED)).isTrue(); assertThat(player.playbackParameters.speed).isEqualTo(speed); } @Test public void setMediaItem() { MediaItem mediaItem = MediaTestUtils.createMediaItem("setMediaItem"); + player.setMediaItem(mediaItem); - assertThat(player.setMediaItemCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEM)).isTrue(); assertThat(player.mediaItem).isSameInstanceAs(mediaItem); } @@ -126,8 +143,11 @@ public void setMediaItem() { public void setMediaItem_withStartPosition() { MediaItem mediaItem = MediaTestUtils.createMediaItem("setMediaItem"); long startPositionMs = 321L; + player.setMediaItem(mediaItem, startPositionMs); - assertThat(player.setMediaItemWithStartPositionCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEM_WITH_START_POSITION)) + .isTrue(); assertThat(player.startPositionMs).isEqualTo(startPositionMs); assertThat(player.mediaItem).isSameInstanceAs(mediaItem); } @@ -136,8 +156,11 @@ public void setMediaItem_withStartPosition() { public void setMediaItem_withResetPosition() { MediaItem mediaItem = MediaTestUtils.createMediaItem("setMediaItem"); boolean resetPosition = true; + player.setMediaItem(mediaItem, resetPosition); - assertThat(player.setMediaItemWithResetPositionCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEM_WITH_RESET_POSITION)) + .isTrue(); assertThat(player.resetPosition).isEqualTo(resetPosition); assertThat(player.mediaItem).isEqualTo(mediaItem); } @@ -145,8 +168,10 @@ public void setMediaItem_withResetPosition() { @Test public void setMediaItems() { List list = MediaTestUtils.createMediaItems(/* size= */ 2); + player.setMediaItems(list); - assertThat(player.setMediaItemsCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS)).isTrue(); assertThat(player.mediaItems).isSameInstanceAs(list); } @@ -154,8 +179,11 @@ public void setMediaItems() { public void setMediaItems_withResetPosition() { List list = MediaTestUtils.createMediaItems(/* size= */ 2); boolean resetPosition = true; + player.setMediaItems(list, resetPosition); - assertThat(player.setMediaItemsWithResetPositionCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION)) + .isTrue(); assertThat(player.resetPosition).isEqualTo(resetPosition); assertThat(player.mediaItems).isSameInstanceAs(list); } @@ -165,8 +193,11 @@ public void setMediaItems_withStartWindowIndex() { List list = MediaTestUtils.createMediaItems(/* size= */ 2); int startWindowIndex = 3; long startPositionMs = 132L; + player.setMediaItems(list, startWindowIndex, startPositionMs); - assertThat(player.setMediaItemsWithStartIndexCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX)) + .isTrue(); assertThat(player.startMediaItemIndex).isEqualTo(startWindowIndex); assertThat(player.startPositionMs).isEqualTo(startPositionMs); assertThat(player.mediaItems).isSameInstanceAs(list); @@ -176,8 +207,10 @@ public void setMediaItems_withStartWindowIndex() { public void setMediaItems_withDuplicatedItems() { List list = MediaTestUtils.createMediaItems(/* size= */ 4); list.set(2, list.get(1)); + player.setMediaItems(list); - assertThat(player.setMediaItemsCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_MEDIA_ITEMS)).isTrue(); assertThat(player.mediaItems).isSameInstanceAs(list); } @@ -187,7 +220,7 @@ public void setPlaylistMetadata() { player.setPlaylistMetadata(playlistMetadata); - assertThat(player.setPlaylistMetadataCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_PLAYLIST_METADATA)).isTrue(); assertThat(player.playlistMetadata).isSameInstanceAs(playlistMetadata); } @@ -197,7 +230,7 @@ public void addMediaItem() { player.addMediaItem(mediaItem); - assertThat(player.addMediaItemCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_ADD_MEDIA_ITEM)).isTrue(); assertThat(player.mediaItem).isSameInstanceAs(mediaItem); } @@ -208,7 +241,7 @@ public void addMediaItem_withIndex() { player.addMediaItem(index, mediaItem); - assertThat(player.addMediaItemWithIndexCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_ADD_MEDIA_ITEM_WITH_INDEX)).isTrue(); assertThat(player.index).isEqualTo(index); assertThat(player.mediaItem).isSameInstanceAs(mediaItem); } @@ -221,7 +254,7 @@ public void addMediaItems() { player.addMediaItems(index, mediaItems); - assertThat(player.addMediaItemsWithIndexCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS_WITH_INDEX)).isTrue(); assertThat(player.index).isEqualTo(index); assertThat(player.mediaItems).isSameInstanceAs(mediaItems); } @@ -234,7 +267,7 @@ public void addMediaItems_withIndex() { player.addMediaItems(index, mediaItems); - assertThat(player.addMediaItemsWithIndexCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_ADD_MEDIA_ITEMS_WITH_INDEX)).isTrue(); assertThat(player.index).isEqualTo(index); assertThat(player.mediaItems).isSameInstanceAs(mediaItems); } @@ -245,7 +278,7 @@ public void removeMediaItem() { player.removeMediaItem(index); - assertThat(player.removeMediaItemCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_REMOVE_MEDIA_ITEM)).isTrue(); assertThat(player.index).isEqualTo(index); } @@ -256,7 +289,7 @@ public void removeMediaItems() { player.removeMediaItems(fromIndex, toIndex); - assertThat(player.removeMediaItemsCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_REMOVE_MEDIA_ITEMS)).isTrue(); assertThat(player.fromIndex).isEqualTo(fromIndex); assertThat(player.toIndex).isEqualTo(toIndex); } @@ -265,7 +298,7 @@ public void removeMediaItems() { public void clearMediaItems() { player.clearMediaItems(); - assertThat(player.clearMediaItemsCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_CLEAR_MEDIA_ITEMS)).isTrue(); } @Test @@ -275,7 +308,7 @@ public void moveMediaItem() { player.moveMediaItem(index, newIndex); - assertThat(player.moveMediaItemCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_MOVE_MEDIA_ITEM)).isTrue(); assertThat(player.index).isEqualTo(index); assertThat(player.newIndex).isEqualTo(newIndex); } @@ -288,7 +321,7 @@ public void moveMediaItems() { player.moveMediaItems(fromIndex, toIndex, newIndex); - assertThat(player.moveMediaItemsCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_MOVE_MEDIA_ITEMS)).isTrue(); assertThat(player.fromIndex).isEqualTo(fromIndex); assertThat(player.toIndex).isEqualTo(toIndex); assertThat(player.newIndex).isEqualTo(newIndex); @@ -297,76 +330,92 @@ public void moveMediaItems() { @Test public void seekToPreviousMediaItem() { player.seekToPreviousMediaItem(); - assertThat(player.seekToPreviousMediaItemCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM)).isTrue(); } @Test public void seekToNextMediaItem() { player.seekToNextMediaItem(); - assertThat(player.seekToNextMediaItemCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_NEXT_MEDIA_ITEM)).isTrue(); } @Test public void seekToPrevious() { player.seekToPrevious(); - assertThat(player.seekToPreviousCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_PREVIOUS)).isTrue(); } @Test public void seekToNext() { player.seekToNext(); - assertThat(player.seekToNextCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SEEK_TO_NEXT)).isTrue(); } @Test public void setShuffleModeEnabled() { boolean testShuffleModeEnabled = true; + player.setShuffleModeEnabled(testShuffleModeEnabled); - assertThat(player.setShuffleModeCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_SHUFFLE_MODE)).isTrue(); assertThat(player.shuffleModeEnabled).isEqualTo(testShuffleModeEnabled); } @Test public void setRepeatMode() { int testRepeatMode = Player.REPEAT_MODE_ALL; + player.setRepeatMode(testRepeatMode); - assertThat(player.setRepeatModeCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_REPEAT_MODE)).isTrue(); assertThat(player.repeatMode).isEqualTo(testRepeatMode); } @Test public void setVolume() { float testVolume = .123f; + player.setVolume(testVolume); - assertThat(player.setVolumeCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_VOLUME)).isTrue(); assertThat(player.volume).isEqualTo(testVolume); } @Test public void setDeviceVolume() { int testVolume = 12; + player.setDeviceVolume(testVolume); - assertThat(player.setDeviceVolumeCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_DEVICE_VOLUME)).isTrue(); assertThat(player.deviceVolume).isEqualTo(testVolume); } @Test public void increaseDeviceVolume() { player.increaseDeviceVolume(); - assertThat(player.increaseDeviceVolumeCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_INCREASE_DEVICE_VOLUME)).isTrue(); } @Test public void decreaseDeviceVolume() { player.decreaseDeviceVolume(); - assertThat(player.decreaseDeviceVolumeCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_DECREASE_DEVICE_VOLUME)).isTrue(); } @Test public void setDeviceMuted() { player.deviceMuted = false; + player.setDeviceMuted(true); - assertThat(player.setDeviceMutedCalled).isTrue(); + + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_DEVICE_MUTED)).isTrue(); assertThat(player.deviceMuted).isTrue(); } @@ -377,7 +426,8 @@ public void setTrackSelectionParameters() { player.setTrackSelectionParameters(trackSelectionParameters); - assertThat(player.setTrackSelectionParametersCalled).isTrue(); + assertThat(player.hasMethodBeenCalled(MockPlayer.METHOD_SET_TRACK_SELECTION_PARAMETERS)) + .isTrue(); assertThat(player.trackSelectionParameters).isSameInstanceAs(trackSelectionParameters); } } diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java index 8a4ec96bee6..5b08c544bb9 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockMediaLibraryService.java @@ -134,8 +134,7 @@ public MediaLibrarySession onGetSession(ControllerInfo controllerInfo) { return (MediaLibrarySession) onGetSessionHandler.onGetSession(controllerInfo); } - MockPlayer player = - new MockPlayer.Builder().setLatchCount(1).setApplicationLooper(handler.getLooper()).build(); + MockPlayer player = new MockPlayer.Builder().setApplicationLooper(handler.getLooper()).build(); MediaLibrarySessionCallback callback = registry.getSessionCallback(); session = diff --git a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java index 416365e414a..9c893431762 100644 --- a/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java +++ b/libraries/test_session_current/src/main/java/androidx/media3/session/MockPlayer.java @@ -15,11 +15,15 @@ */ package androidx.media3.session; +import static androidx.media3.common.util.Assertions.checkNotNull; +import static java.lang.annotation.ElementType.TYPE_USE; + import android.os.Looper; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; +import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.collection.ArraySet; import androidx.media3.common.AudioAttributes; @@ -37,20 +41,162 @@ import androidx.media3.common.TracksInfo; import androidx.media3.common.VideoSize; import androidx.media3.common.text.Cue; +import androidx.media3.common.util.ConditionVariable; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.List; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; /** A mock implementation of {@link Player} for testing. */ @UnstableApi public class MockPlayer implements Player { - public final CountDownLatch countDownLatch; + /** Player methods. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({ + METHOD_ADD_MEDIA_ITEM, + METHOD_ADD_MEDIA_ITEMS, + METHOD_ADD_MEDIA_ITEM_WITH_INDEX, + METHOD_ADD_MEDIA_ITEMS_WITH_INDEX, + METHOD_CLEAR_MEDIA_ITEMS, + METHOD_DECREASE_DEVICE_VOLUME, + METHOD_INCREASE_DEVICE_VOLUME, + METHOD_MOVE_MEDIA_ITEM, + METHOD_MOVE_MEDIA_ITEMS, + METHOD_PAUSE, + METHOD_PLAY, + METHOD_PREPARE, + METHOD_RELEASE, + METHOD_REMOVE_MEDIA_ITEM, + METHOD_REMOVE_MEDIA_ITEMS, + METHOD_SEEK_BACK, + METHOD_SEEK_FORWARD, + METHOD_SEEK_TO, + METHOD_SEEK_TO_DEFAULT_POSITION, + METHOD_SEEK_TO_DEFAULT_POSITION_WITH_MEDIA_ITEM_INDEX, + METHOD_SEEK_TO_NEXT, + METHOD_SEEK_TO_NEXT_MEDIA_ITEM, + METHOD_SEEK_TO_PREVIOUS, + METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM, + METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX, + METHOD_SET_DEVICE_MUTED, + METHOD_SET_DEVICE_VOLUME, + METHOD_SET_MEDIA_ITEM, + METHOD_SET_MEDIA_ITEM_WITH_RESET_POSITION, + METHOD_SET_MEDIA_ITEM_WITH_START_POSITION, + METHOD_SET_MEDIA_ITEMS, + METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, + METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, + METHOD_SET_PLAY_WHEN_READY, + METHOD_SET_PLAYBACK_PARAMETERS, + METHOD_SET_PLAYBACK_SPEED, + METHOD_SET_PLAYLIST_METADATA, + METHOD_SET_REPEAT_MODE, + METHOD_SET_SHUFFLE_MODE, + METHOD_SET_TRACK_SELECTION_PARAMETERS, + METHOD_SET_VOLUME, + METHOD_STOP + }) + public @interface Method {} + + /** Maps to {@link Player#addMediaItem(MediaItem)}. */ + public static final int METHOD_ADD_MEDIA_ITEM = 0; + /** Maps to {@link Player#addMediaItems(List)}. */ + public static final int METHOD_ADD_MEDIA_ITEMS = 1; + /** Maps to {@link Player#addMediaItem(int, MediaItem)}. */ + public static final int METHOD_ADD_MEDIA_ITEM_WITH_INDEX = 2; + /** Maps to {@link Player#addMediaItems(int, List)}. */ + public static final int METHOD_ADD_MEDIA_ITEMS_WITH_INDEX = 3; + /** Maps to {@link Player#clearMediaItems()}. */ + public static final int METHOD_CLEAR_MEDIA_ITEMS = 4; + /** Maps to {@link Player#decreaseDeviceVolume()}. */ + public static final int METHOD_DECREASE_DEVICE_VOLUME = 5; + /** Maps to {@link Player#increaseDeviceVolume()}. */ + public static final int METHOD_INCREASE_DEVICE_VOLUME = 6; + /** Maps to {@link Player#moveMediaItem(int, int)}. */ + public static final int METHOD_MOVE_MEDIA_ITEM = 7; + /** Maps to {@link Player#moveMediaItems(int, int, int)}. */ + public static final int METHOD_MOVE_MEDIA_ITEMS = 8; + /** Maps to {@link Player#pause()}. */ + public static final int METHOD_PAUSE = 9; + /** Maps to {@link Player#play()}. */ + public static final int METHOD_PLAY = 10; + /** Maps to {@link Player#prepare()}. */ + public static final int METHOD_PREPARE = 11; + /** Maps to {@link Player#release()}. */ + public static final int METHOD_RELEASE = 12; + /** Maps to {@link Player#removeMediaItem(int)}. */ + public static final int METHOD_REMOVE_MEDIA_ITEM = 13; + /** Maps to {@link Player#removeMediaItems(int, int)}. */ + public static final int METHOD_REMOVE_MEDIA_ITEMS = 14; + /** Maps to {@link Player#seekBack()}. */ + public static final int METHOD_SEEK_BACK = 15; + /** Maps to {@link Player#seekForward()}. */ + public static final int METHOD_SEEK_FORWARD = 16; + /** Maps to {@link Player#seekTo(long)}. */ + public static final int METHOD_SEEK_TO = 17; + /** Maps to {@link Player#seekToDefaultPosition()}. */ + public static final int METHOD_SEEK_TO_DEFAULT_POSITION = 18; + /** Maps to {@link Player#seekToDefaultPosition(int)}. */ + public static final int METHOD_SEEK_TO_DEFAULT_POSITION_WITH_MEDIA_ITEM_INDEX = 19; + /** Maps to {@link Player#seekToNext()}. */ + public static final int METHOD_SEEK_TO_NEXT = 20; + /** Maps to {@link Player#seekToNextMediaItem()}. */ + public static final int METHOD_SEEK_TO_NEXT_MEDIA_ITEM = 21; + /** Maps to {@link Player#seekToPrevious()}. */ + public static final int METHOD_SEEK_TO_PREVIOUS = 22; + /** Maps to {@link Player#seekToPreviousMediaItem()}. */ + public static final int METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM = 23; + /** Maps to {@link Player#seekTo(int, long)}. */ + public static final int METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX = 24; + /** Maps to {@link Player#setDeviceMuted(boolean)}. */ + public static final int METHOD_SET_DEVICE_MUTED = 25; + /** Maps to {@link Player#setDeviceVolume(int)}. */ + public static final int METHOD_SET_DEVICE_VOLUME = 26; + /** Maps to {@link Player#setMediaItem(MediaItem)}. */ + public static final int METHOD_SET_MEDIA_ITEM = 27; + /** Maps to {@link Player#setMediaItem(MediaItem, boolean)}. */ + public static final int METHOD_SET_MEDIA_ITEM_WITH_RESET_POSITION = 28; + /** Maps to {@link Player#setMediaItem(MediaItem, long)}. */ + public static final int METHOD_SET_MEDIA_ITEM_WITH_START_POSITION = 29; + /** Maps to {@link Player#setMediaItems(List)}. */ + public static final int METHOD_SET_MEDIA_ITEMS = 30; + /** Maps to {@link Player#setMediaItems(List, boolean)}. */ + public static final int METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION = 31; + /** Maps to {@link Player#setMediaItems(List, int, long)}. */ + public static final int METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX = 32; + /** Maps to {@link Player#setPlayWhenReady(boolean)}. */ + public static final int METHOD_SET_PLAY_WHEN_READY = 33; + /** Maps to {@link Player#setPlaybackParameters(PlaybackParameters)}. */ + public static final int METHOD_SET_PLAYBACK_PARAMETERS = 34; + /** Maps to {@link Player#setPlaybackSpeed(float)}. */ + public static final int METHOD_SET_PLAYBACK_SPEED = 35; + /** Maps to {@link Player#setPlaylistMetadata(MediaMetadata)}. */ + public static final int METHOD_SET_PLAYLIST_METADATA = 36; + /** Maps to {@link Player#setRepeatMode(int)}. */ + public static final int METHOD_SET_REPEAT_MODE = 37; + /** Maps to {@link Player#setShuffleModeEnabled(boolean)}. */ + public static final int METHOD_SET_SHUFFLE_MODE = 38; + /** Maps to {@link Player#setTrackSelectionParameters(TrackSelectionParameters)}. */ + public static final int METHOD_SET_TRACK_SELECTION_PARAMETERS = 39; + /** Maps to {@link Player#setVolume(float)}. */ + public static final int METHOD_SET_VOLUME = 40; + /** Maps to {@link Player#stop()}. */ + public static final int METHOD_STOP = 41; + private final boolean changePlayerStateWithTransportControl; private final Looper applicationLooper; private final ArraySet listeners = new ArraySet<>(); + private final ImmutableMap<@Method Integer, ConditionVariable> conditionVariables = + createMethodConditionVariables(); @Nullable PlaybackException playerError; public AudioAttributes audioAttributes; @@ -106,51 +252,7 @@ public class MockPlayer implements Player { public long maxSeekToPreviousPositionMs; public TrackSelectionParameters trackSelectionParameters; - public boolean playCalled; - public boolean pauseCalled; - public boolean prepareCalled; - public boolean stopCalled; - public boolean releaseCalled; - public boolean seekToDefaultPositionCalled; - public boolean seekToDefaultPositionWithMediaItemIndexCalled; - public boolean seekToCalled; - public boolean seekToWithMediaItemIndexCalled; - public boolean setPlaybackSpeedCalled; - public boolean setPlaybackParametersCalled; - public boolean setMediaItemCalled; - public boolean setMediaItemWithStartPositionCalled; - public boolean setMediaItemWithResetPositionCalled; - public boolean setMediaItemsCalled; - public boolean setMediaItemsWithResetPositionCalled; - public boolean setMediaItemsWithStartIndexCalled; - public boolean setPlaylistMetadataCalled; - public boolean addMediaItemCalled; - public boolean addMediaItemWithIndexCalled; - public boolean addMediaItemsCalled; - public boolean addMediaItemsWithIndexCalled; - public boolean removeMediaItemCalled; - public boolean removeMediaItemsCalled; - public boolean clearMediaItemsCalled; - public boolean moveMediaItemCalled; - public boolean moveMediaItemsCalled; - public boolean seekToPreviousMediaItemCalled; - public boolean seekToNextMediaItemCalled; - public boolean seekToPreviousCalled; - public boolean seekToNextCalled; - public boolean setRepeatModeCalled; - public boolean setShuffleModeCalled; - public boolean setVolumeCalled; - public boolean setDeviceVolumeCalled; - public boolean increaseDeviceVolumeCalled; - public boolean decreaseDeviceVolumeCalled; - public boolean setDeviceMutedCalled; - public boolean setPlayWhenReadyCalled; - public boolean seekBackCalled; - public boolean seekForwardCalled; - public boolean setTrackSelectionParametersCalled; - private MockPlayer(Builder builder) { - countDownLatch = new CountDownLatch(builder.latchCount); changePlayerStateWithTransportControl = builder.changePlayerStateWithTransportControl; applicationLooper = builder.applicationLooper; @@ -203,13 +305,12 @@ private MockPlayer(Builder builder) { @Override public void release() { - releaseCalled = true; + checkNotNull(conditionVariables.get(METHOD_RELEASE)).open(); } @Override public void stop() { - stopCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_STOP)).open(); } @Deprecated @@ -236,8 +337,7 @@ public PlaybackException getPlayerError() { @Override public void play() { - playCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_PLAY)).open(); if (changePlayerStateWithTransportControl) { notifyPlayWhenReadyChanged( /* playWhenReady= */ true, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); @@ -246,8 +346,7 @@ public void play() { @Override public void pause() { - pauseCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_PAUSE)).open(); if (changePlayerStateWithTransportControl) { notifyPlayWhenReadyChanged( /* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST); @@ -256,8 +355,7 @@ public void pause() { @Override public void prepare() { - prepareCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_PREPARE)).open(); if (changePlayerStateWithTransportControl) { notifyPlaybackStateChanged(Player.STATE_READY); } @@ -265,30 +363,27 @@ public void prepare() { @Override public void seekToDefaultPosition() { - seekToDefaultPositionCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO_DEFAULT_POSITION)).open(); } @Override public void seekToDefaultPosition(int mediaItemIndex) { - seekToDefaultPositionWithMediaItemIndexCalled = true; seekMediaItemIndex = mediaItemIndex; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO_DEFAULT_POSITION_WITH_MEDIA_ITEM_INDEX)) + .open(); } @Override public void seekTo(long positionMs) { - seekToCalled = true; seekPositionMs = positionMs; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO)).open(); } @Override public void seekTo(int mediaItemIndex, long positionMs) { - seekToWithMediaItemIndexCalled = true; seekMediaItemIndex = mediaItemIndex; seekPositionMs = positionMs; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX)).open(); } @Override @@ -298,8 +393,7 @@ public long getSeekBackIncrement() { @Override public void seekBack() { - seekBackCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_BACK)).open(); } @Override @@ -309,8 +403,7 @@ public long getSeekForwardIncrement() { @Override public void seekForward() { - seekForwardCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_FORWARD)).open(); } @Override @@ -508,16 +601,14 @@ public AudioAttributes getAudioAttributes() { @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { - setPlaybackParametersCalled = true; this.playbackParameters = playbackParameters; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_PLAYBACK_PARAMETERS)).open(); } @Override public void setPlaybackSpeed(float speed) { - setPlaybackSpeedCalled = true; playbackParameters = new PlaybackParameters(speed); - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_PLAYBACK_SPEED)).open(); } @Override @@ -527,9 +618,8 @@ public float getVolume() { @Override public void setVolume(float volume) { - setVolumeCalled = true; this.volume = volume; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_VOLUME)).open(); } @Override @@ -554,35 +644,30 @@ public boolean isDeviceMuted() { @Override public void setDeviceVolume(int volume) { - setDeviceVolumeCalled = true; deviceVolume = volume; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_DEVICE_VOLUME)).open(); } @Override public void increaseDeviceVolume() { - increaseDeviceVolumeCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_INCREASE_DEVICE_VOLUME)).open(); } @Override public void decreaseDeviceVolume() { - decreaseDeviceVolumeCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_DECREASE_DEVICE_VOLUME)).open(); } @Override public void setDeviceMuted(boolean muted) { - setDeviceMutedCalled = true; deviceMuted = muted; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_DEVICE_MUTED)).open(); } @Override public void setPlayWhenReady(boolean playWhenReady) { - this.setPlayWhenReadyCalled = true; this.playWhenReady = playWhenReady; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_PLAY_WHEN_READY)).open(); } @Override @@ -626,49 +711,43 @@ public Timeline getCurrentTimeline() { @Override public void setMediaItem(MediaItem mediaItem) { - setMediaItemCalled = true; this.mediaItem = mediaItem; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_MEDIA_ITEM)).open(); } @Override public void setMediaItem(MediaItem mediaItem, long startPositionMs) { - setMediaItemWithStartPositionCalled = true; this.mediaItem = mediaItem; this.startPositionMs = startPositionMs; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_MEDIA_ITEM_WITH_START_POSITION)).open(); } @Override public void setMediaItem(MediaItem mediaItem, boolean resetPosition) { - setMediaItemWithResetPositionCalled = true; this.mediaItem = mediaItem; this.resetPosition = resetPosition; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_MEDIA_ITEM_WITH_RESET_POSITION)).open(); } @Override public void setMediaItems(List mediaItems) { - setMediaItemsCalled = true; this.mediaItems = mediaItems; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_MEDIA_ITEMS)).open(); } @Override public void setMediaItems(List mediaItems, boolean resetPosition) { - setMediaItemsWithResetPositionCalled = true; this.mediaItems = mediaItems; this.resetPosition = resetPosition; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION)).open(); } @Override public void setMediaItems(List mediaItems, int startIndex, long startPositionMs) { - setMediaItemsWithStartIndexCalled = true; this.mediaItems = mediaItems; this.startMediaItemIndex = startIndex; this.startPositionMs = startPositionMs; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX)).open(); } @Override @@ -678,9 +757,8 @@ public MediaMetadata getPlaylistMetadata() { @Override public void setPlaylistMetadata(MediaMetadata playlistMetadata) { - setPlaylistMetadataCalled = true; this.playlistMetadata = playlistMetadata; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_PLAYLIST_METADATA)).open(); } @Deprecated @@ -775,70 +853,61 @@ public int getNextMediaItemIndex() { @Override public void addMediaItem(MediaItem mediaItem) { - addMediaItemCalled = true; this.mediaItem = mediaItem; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_ADD_MEDIA_ITEM)).open(); } @Override public void addMediaItem(int index, MediaItem mediaItem) { - addMediaItemWithIndexCalled = true; this.index = index; this.mediaItem = mediaItem; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_ADD_MEDIA_ITEM_WITH_INDEX)).open(); } @Override public void addMediaItems(List mediaItems) { - addMediaItemsCalled = true; this.mediaItems = mediaItems; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_ADD_MEDIA_ITEMS)).open(); } @Override public void addMediaItems(int index, List mediaItems) { - addMediaItemsWithIndexCalled = true; this.index = index; this.mediaItems = mediaItems; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_ADD_MEDIA_ITEMS_WITH_INDEX)).open(); } @Override public void removeMediaItem(int index) { - removeMediaItemCalled = true; this.index = index; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_REMOVE_MEDIA_ITEM)).open(); } @Override public void removeMediaItems(int fromIndex, int toIndex) { - removeMediaItemsCalled = true; this.fromIndex = fromIndex; this.toIndex = toIndex; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_REMOVE_MEDIA_ITEMS)).open(); } @Override public void clearMediaItems() { - clearMediaItemsCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_CLEAR_MEDIA_ITEMS)).open(); } @Override public void moveMediaItem(int currentIndex, int newIndex) { - moveMediaItemCalled = true; this.index = currentIndex; this.newIndex = newIndex; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_MOVE_MEDIA_ITEM)).open(); } @Override public void moveMediaItems(int fromIndex, int toIndex, int newIndex) { - moveMediaItemsCalled = true; this.fromIndex = fromIndex; this.toIndex = toIndex; this.newIndex = newIndex; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_MOVE_MEDIA_ITEMS)).open(); } @Deprecated @@ -901,20 +970,17 @@ public void seekToNextWindow() { @Override public void seekToPreviousMediaItem() { - seekToPreviousMediaItemCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM)).open(); } @Override public void seekToNextMediaItem() { - seekToNextMediaItemCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO_NEXT_MEDIA_ITEM)).open(); } @Override public void seekToPrevious() { - seekToPreviousCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO_PREVIOUS)).open(); } @Override @@ -924,8 +990,7 @@ public long getMaxSeekToPreviousPosition() { @Override public void seekToNext() { - seekToNextCalled = true; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SEEK_TO_NEXT)).open(); } @Override @@ -935,9 +1000,8 @@ public int getRepeatMode() { @Override public void setRepeatMode(int repeatMode) { - setRepeatModeCalled = true; this.repeatMode = repeatMode; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_REPEAT_MODE)).open(); } @Override @@ -947,9 +1011,8 @@ public boolean getShuffleModeEnabled() { @Override public void setShuffleModeEnabled(boolean shuffleModeEnabled) { - setShuffleModeCalled = true; this.shuffleModeEnabled = shuffleModeEnabled; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_SHUFFLE_MODE)).open(); } @Override @@ -1001,9 +1064,6 @@ public void notifyPlaylistMetadataChanged() { @Override public VideoSize getVideoSize() { - if (videoSize == null) { - videoSize = VideoSize.UNKNOWN; - } return videoSize; } @@ -1134,9 +1194,8 @@ public TrackSelectionParameters getTrackSelectionParameters() { @Override public void setTrackSelectionParameters(TrackSelectionParameters parameters) { - setTrackSelectionParametersCalled = true; trackSelectionParameters = parameters; - countDownLatch.countDown(); + checkNotNull(conditionVariables.get(METHOD_SET_TRACK_SELECTION_PARAMETERS)).open(); } @Override @@ -1144,10 +1203,73 @@ public Looper getApplicationLooper() { return applicationLooper; } + /** Returns whether {@code method} has been called at least once. */ + public boolean hasMethodBeenCalled(@Method int method) { + return checkNotNull(conditionVariables.get(method)).isOpen(); + } + + /** + * Awaits up to {@code timeOutMs} until {@code method} is called, otherwise throws a {@link + * TimeoutException}. + */ + public void awaitMethodCalled(@Method int method, long timeOutMs) + throws TimeoutException, InterruptedException { + if (!checkNotNull(conditionVariables.get(method)).block(timeOutMs)) { + throw new TimeoutException( + Util.formatInvariant("Method %d not called after %f ms", method, timeOutMs)); + } + } + + private static ImmutableMap<@Method Integer, ConditionVariable> createMethodConditionVariables() { + return new ImmutableMap.Builder<@Method Integer, ConditionVariable>() + .put(METHOD_ADD_MEDIA_ITEM, new ConditionVariable()) + .put(METHOD_ADD_MEDIA_ITEMS, new ConditionVariable()) + .put(METHOD_ADD_MEDIA_ITEM_WITH_INDEX, new ConditionVariable()) + .put(METHOD_ADD_MEDIA_ITEMS_WITH_INDEX, new ConditionVariable()) + .put(METHOD_CLEAR_MEDIA_ITEMS, new ConditionVariable()) + .put(METHOD_DECREASE_DEVICE_VOLUME, new ConditionVariable()) + .put(METHOD_INCREASE_DEVICE_VOLUME, new ConditionVariable()) + .put(METHOD_MOVE_MEDIA_ITEM, new ConditionVariable()) + .put(METHOD_MOVE_MEDIA_ITEMS, new ConditionVariable()) + .put(METHOD_PAUSE, new ConditionVariable()) + .put(METHOD_PLAY, new ConditionVariable()) + .put(METHOD_PREPARE, new ConditionVariable()) + .put(METHOD_RELEASE, new ConditionVariable()) + .put(METHOD_REMOVE_MEDIA_ITEM, new ConditionVariable()) + .put(METHOD_REMOVE_MEDIA_ITEMS, new ConditionVariable()) + .put(METHOD_SEEK_BACK, new ConditionVariable()) + .put(METHOD_SEEK_FORWARD, new ConditionVariable()) + .put(METHOD_SEEK_TO, new ConditionVariable()) + .put(METHOD_SEEK_TO_DEFAULT_POSITION, new ConditionVariable()) + .put(METHOD_SEEK_TO_DEFAULT_POSITION_WITH_MEDIA_ITEM_INDEX, new ConditionVariable()) + .put(METHOD_SEEK_TO_NEXT, new ConditionVariable()) + .put(METHOD_SEEK_TO_NEXT_MEDIA_ITEM, new ConditionVariable()) + .put(METHOD_SEEK_TO_PREVIOUS, new ConditionVariable()) + .put(METHOD_SEEK_TO_PREVIOUS_MEDIA_ITEM, new ConditionVariable()) + .put(METHOD_SEEK_TO_WITH_MEDIA_ITEM_INDEX, new ConditionVariable()) + .put(METHOD_SET_DEVICE_MUTED, new ConditionVariable()) + .put(METHOD_SET_DEVICE_VOLUME, new ConditionVariable()) + .put(METHOD_SET_MEDIA_ITEM, new ConditionVariable()) + .put(METHOD_SET_MEDIA_ITEM_WITH_RESET_POSITION, new ConditionVariable()) + .put(METHOD_SET_MEDIA_ITEM_WITH_START_POSITION, new ConditionVariable()) + .put(METHOD_SET_MEDIA_ITEMS, new ConditionVariable()) + .put(METHOD_SET_MEDIA_ITEMS_WITH_RESET_POSITION, new ConditionVariable()) + .put(METHOD_SET_MEDIA_ITEMS_WITH_START_INDEX, new ConditionVariable()) + .put(METHOD_SET_PLAY_WHEN_READY, new ConditionVariable()) + .put(METHOD_SET_PLAYBACK_PARAMETERS, new ConditionVariable()) + .put(METHOD_SET_PLAYBACK_SPEED, new ConditionVariable()) + .put(METHOD_SET_PLAYLIST_METADATA, new ConditionVariable()) + .put(METHOD_SET_REPEAT_MODE, new ConditionVariable()) + .put(METHOD_SET_SHUFFLE_MODE, new ConditionVariable()) + .put(METHOD_SET_TRACK_SELECTION_PARAMETERS, new ConditionVariable()) + .put(METHOD_SET_VOLUME, new ConditionVariable()) + .put(METHOD_STOP, new ConditionVariable()) + .buildOrThrow(); + } + /** Builder for {@link MockPlayer}. */ public static final class Builder { - private int latchCount; private boolean changePlayerStateWithTransportControl; private Looper applicationLooper; private int itemCount; @@ -1156,11 +1278,6 @@ public Builder() { applicationLooper = Util.getCurrentOrMainLooper(); } - public Builder setLatchCount(int latchCount) { - this.latchCount = latchCount; - return this; - } - public Builder setChangePlayerStateWithTransportControl( boolean changePlayerStateWithTransportControl) { this.changePlayerStateWithTransportControl = changePlayerStateWithTransportControl; diff --git a/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java b/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java index ed6f7412f4b..589187df0e9 100644 --- a/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java +++ b/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java @@ -156,22 +156,10 @@ *

  • Corresponding method: {@link #setKeepContentOnPlayerReset(boolean)} *
  • Default: {@code false} * - *
  • {@code player_layout_id} - Specifies the id of the layout to be inflated. See below - * for more details. - *
      - *
    • Corresponding method: None - *
    • Default: {@code R.layout.exo_player_view} - *
    - *
  • {@code controller_layout_id} - Specifies the id of the layout resource to be - * inflated by the child {@link PlayerControlView}. See below for more details. - *
      - *
    • Corresponding method: None - *
    • Default: {@code R.layout.exo_player_control_view} - *
    *
  • All attributes that can be set on {@link PlayerControlView} and {@link DefaultTimeBar} can * also be set on a PlayerView, and will be propagated to the inflated {@link * PlayerControlView} unless the layout is overridden to specify a custom {@code - * exo_controller} (see below). + * exo_controller}. * * *

    Overriding drawables