From c5b046900d1bb8a72cfe4daee713a2b12d729173 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 23 Feb 2022 16:02:50 +0000 Subject: [PATCH 01/20] Update gradle wrapper to 7.4 #minor-release PiperOrigin-RevId: 430454251 (cherry picked from commit e8096f07b9a458310589685b6019d5d79cbde433) --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1e62b660612..d584d8cbe4c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-7.4-all.zip From 7125a3361b133f4ff0c93952bfdf1ca46b68eb56 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 24 Feb 2022 11:54:37 +0000 Subject: [PATCH 02/20] Update the gradle wrapper scripts Generated by running: ./gradlew wrapper --gradle-version 7.4 --distribution-type all #minor-release PiperOrigin-RevId: 430666317 (cherry picked from commit 92af42bf2588e9f9a102b8f6812e140e077f2d6a) --- gradle/wrapper/gradle-wrapper.jar | Bin 51026 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 294 ++++++++++++++--------- gradlew.bat | 179 +++++++------- 4 files changed, 272 insertions(+), 204 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 747bb13173370f361efaaf7935f6061d1dc2c61d..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch literal 59821 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 literal 51026 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 '} + 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 From d10700456de5dc005dbf90c92eb6bf9d08cfde42 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 24 Feb 2022 20:59:58 +0000 Subject: [PATCH 03/20] FMP4: Fix output of mixed v0 and v1 emsg samples Issue: google/ExoPlayer#9996 #minor-release PiperOrigin-RevId: 430773329 (cherry picked from commit 5a304fdbd9bc8af8229b1a9cddc79f359fe8e215) --- .../extractor/mp4/FragmentedMp4Extractor.java | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) 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; } } From 16819edebdebf68f41f526ab58b426b5b6e91ce0 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 25 Feb 2022 11:35:30 +0000 Subject: [PATCH 04/20] Remove unecessary `git checkout` command from README The command is not needed, because the specified branch is already the default branch on GitHub so will be checked out by clone automatically. PiperOrigin-RevId: 430910549 (cherry picked from commit d34221519d907f002abce245a3450ccacf988de8) --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index cf07b97ad84..92496835d21 100644 --- a/README.md +++ b/README.md @@ -96,13 +96,11 @@ 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. -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 From a8fd90ec844cdf4ce3985258c2e9ea2097d416d1 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 25 Feb 2022 11:40:27 +0000 Subject: [PATCH 05/20] Update media3 docs with details of new release branch PiperOrigin-RevId: 430911179 (cherry picked from commit 7d389361063059656ee54c7297c99a76683b19bd) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92496835d21..7ea16a8f429 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ 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: @@ -127,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 From ca50beee6c3b34b28a23745e027104678271ea80 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 25 Feb 2022 15:51:00 +0000 Subject: [PATCH 06/20] Remove duplicate media3 release note and fix formatting PiperOrigin-RevId: 430946606 (cherry picked from commit 5d0c7b91d042970811b5fcc095c4148542b31cf8) --- RELEASENOTES.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8aed9050a46..1c9ead7db7a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -63,11 +63,11 @@ 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 @@ -120,7 +120,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 +134,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)). @@ -159,7 +158,9 @@ 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)). From bc1bcab553e28e4ec0aaafc10830faf367140939 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 25 Feb 2022 16:37:49 +0000 Subject: [PATCH 07/20] Remove exoplayer-only release note from media3 There's no media3 equivalent to the `com.google.android.exoplayer:exoplayer` dependency. PiperOrigin-RevId: 430955037 (cherry picked from commit 8ae74ad873e18cd8510b9a398a264b3359766347) --- RELEASENOTES.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1c9ead7db7a..142dea70104 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -153,10 +153,6 @@ * 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 From 6f5206cd76533fbe66a12e84ac153f5248725e22 Mon Sep 17 00:00:00 2001 From: bachinger Date: Sun, 27 Feb 2022 15:18:03 +0000 Subject: [PATCH 08/20] Drop ads for which we don't have metadata when joining a live stream When a live stream is joined while ads are already playing, the LOADED event is missed and we don't have ad information for those ads in the ad group that are before the ad index at which we joined. This way we can clip the duration when we receive the LOADED event for the last ad in the group. This fixes the problem of the playback controls being hidden when content resumes after the ad group. #minor-release PiperOrigin-RevId: 431269627 (cherry picked from commit 8e8c59031c328e4c64a16193732078568712a632) --- .../ImaServerSideAdInsertionMediaSource.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) 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(), From 405795ca98a372b2c579b142de2be986d46e04f7 Mon Sep 17 00:00:00 2001 From: ibaker Date: Mon, 28 Feb 2022 09:30:36 +0000 Subject: [PATCH 09/20] Cross-reference the corresponding media3 and exoplayer releases PiperOrigin-RevId: 431376857 (cherry picked from commit 3e9dfaa5865217420c2ac5dc8f103a7c2d9f152d) --- RELEASENOTES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 142dea70104..72b1b32b33d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,9 @@ ### 1.0.0-alpha02 (2022-03-09) +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()` so that subclasses of `DefaultRenderersFactory` that override From 291c95daa025428d3c4cad36af977177127fd0b7 Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 28 Feb 2022 11:03:58 +0000 Subject: [PATCH 10/20] Accept page index 0 for getChildren() in MediaLibraryServiceLegacyStub This is consistent with the new MediaSessionStub that accepts page index 0 and the JavaDoc of legacy and new service callbacks. Issue: androidx/media#32 PiperOrigin-RevId: 431390454 (cherry picked from commit 9821dd282c05dde945d787b8c1d4259fbc22bfef) --- .../media3/session/MediaLibraryServiceLegacyStub.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); }); } From 49e6fa805a7f1a54e60921bc716b2a73c4056157 Mon Sep 17 00:00:00 2001 From: Ian Baker Date: Tue, 1 Mar 2022 09:44:52 +0000 Subject: [PATCH 11/20] Merge pull request #10011 from tonykwok:dev-v2 PiperOrigin-RevId: 431395359 (cherry picked from commit c961ea1ca7c119b309456033021434dd38ad0e84) --- .../media3/exoplayer/drm/UnsupportedDrmException.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 {} From 8e386ef21c717b59c6f928d4088e3f88137285e7 Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 3 Mar 2022 11:35:44 +0000 Subject: [PATCH 12/20] More 2.17.0 release note fixes #minor-release PiperOrigin-RevId: 432154626 (cherry picked from commit 986928a89c7291ce997153e1b9351f2b4fb94f69) --- RELEASENOTES.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 72b1b32b33d..fa6d929e698 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -21,7 +21,7 @@ This release corresponds to the 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)). @@ -74,9 +74,9 @@ This release corresponds to the * 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 @@ -178,38 +178,38 @@ This release corresponds to the ([#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 From d20160d7513ecbb011ebf496612f4a81cf3e235b Mon Sep 17 00:00:00 2001 From: ibaker Date: Thu, 3 Mar 2022 13:20:51 +0000 Subject: [PATCH 13/20] Deprecate SingleSampleMediaSource.Factory#setTrackId This method is no longer needed since we added SubtitleConfiguration#id in https://github.com/androidx/media/commit/59d98b9a4e2381647f3e4552b6935bcca1be6f89. Issue: google/ExoPlayer#10016 #minor-release PiperOrigin-RevId: 432169262 (cherry picked from commit 232f2d815d4bf306bddca033b8b5a1454af8601f) --- .../source/SingleSampleMediaSource.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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, From 7afaf97489aaa301ccd97f1d14bccc5479b3932b Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 3 Mar 2022 15:29:15 +0000 Subject: [PATCH 14/20] Fix E-AC3 output capability check without sample rate #minor-release PiperOrigin-RevId: 432189509 (cherry picked from commit a73a9e9ca5e1a93518443e3e20953727a4189e9a) --- .../androidx/media3/exoplayer/audio/DefaultAudioSink.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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; From 25004f89885d570eb0746043c8cbd68edf1f0eab Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 4 Mar 2022 10:35:20 +0000 Subject: [PATCH 15/20] Remove CountDownLatch from MockPlayer The MockPlayer has a single CountDownLatch field and multiple boolean flags that track if a player method was called. Upon calling the methods the latch count. Tests set the latch count to match exactly with the number of expected player interactions then block the test thread until the latch reaches zero and assert the respective method flags are true. This is subject to false positives. If the underneath implementation changes and call more player method, then the test thread will unblock as soon as a certain number of interactions is performed, which may be less than what the test expected originally. However, the test may stil pass if the player thread had enough time to update the expected method flag. This change removes the single CountDownLatch and the boolean flags and instead it adds APIs to query the MockPlayer if a method has been called and await until a method is called. Internally, the MockPlayer has a ConditionVariable per method. PiperOrigin-RevId: 432399077 (cherry picked from commit 45d512160c7c14eda33785a632a1fc3eebc9769d) --- .../MediaSessionAndControllerTest.java | 4 +- .../session/MediaSessionCallbackTest.java | 10 +- ...CallbackWithMediaControllerCompatTest.java | 127 +++--- .../session/MediaSessionKeyEventTest.java | 40 +- .../session/MediaSessionPermissionTest.java | 1 - .../session/MediaSessionPlayerTest.java | 163 ++++---- .../media3/session/MediaSessionTest.java | 6 +- .../media3/session/MockPlayerTest.java | 130 ++++-- .../session/MockMediaLibraryService.java | 3 +- .../androidx/media3/session/MockPlayer.java | 393 ++++++++++++------ 10 files changed, 513 insertions(+), 364 deletions(-) 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..5ea89cf1b5d 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(); @@ -143,43 +142,43 @@ private void dispatchMediaKeyEvent(int keyCode, boolean doubleTap) { @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 +187,19 @@ 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 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..68a6fb68304 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 @@ -101,7 +101,6 @@ private void createSessionWithAvailableCommands( SessionCommands sessionCommands, Player.Commands playerCommands) { player = new MockPlayer.Builder() - .setLatchCount(1) .setApplicationLooper(threadTestRule.getHandler().getLooper()) .build(); callback = 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..8006eb40121 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(); @@ -96,53 +93,56 @@ public void cleanUp() { @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 +150,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 +159,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 +170,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 +181,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 +198,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 +214,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 +230,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 +242,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 +253,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 +266,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 +277,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 +292,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 +308,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 +318,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 +329,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 +341,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 +353,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 +364,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 +375,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 +384,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 +394,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 +407,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 +416,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 +488,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 +497,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 +514,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 +531,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..11dad583acb 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 @@ -78,8 +78,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( @@ -394,8 +393,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; From 50550ab191b47c128e292cab5cb32ea20cf8aab8 Mon Sep 17 00:00:00 2001 From: ibaker Date: Fri, 4 Mar 2022 12:05:31 +0000 Subject: [PATCH 16/20] Remove media3 PlayerView javadoc references to overriding layouts These should have been removed as part of https://github.com/androidx/media/commit/1391b7c65dab1856dad363dff7c0a250245f1671, since we no longer officially support overriding the layout file for this class. This class is known as StyledPlayerView in exoplayer2. #minor-release PiperOrigin-RevId: 432411322 (cherry picked from commit a353b3332abefc798d9751f037e7ef7a78ecbeae) --- .../main/java/androidx/media3/ui/PlayerView.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) 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

    From 4456a865cc0db7bdbfd01072f309d6dad3da5c1c Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 4 Mar 2022 14:24:42 +0000 Subject: [PATCH 17/20] Misc cleanup in session tests PiperOrigin-RevId: 432430345 (cherry picked from commit 8e98187a1e08167cbef08c94e632c8d84a4667f1) --- .../session/MediaSessionKeyEventTest.java | 20 ++-- .../session/MediaSessionPermissionTest.java | 98 +++++++++---------- .../session/MediaSessionPlayerTest.java | 7 +- .../media3/session/MediaSessionTest.java | 11 +++ 4 files changed, 73 insertions(+), 63 deletions(-) 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 5ea89cf1b5d..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 @@ -119,7 +119,7 @@ public void setUp() throws Exception { } @After - public void cleanUp() throws Exception { + public void tearDown() throws Exception { handler.postAndSync( () -> { if (mediaPlayer != null) { @@ -130,15 +130,6 @@ 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); @@ -202,6 +193,15 @@ public void playPauseKeyEvent_doubleTapIsTranslatedToSkipToNext() throws Excepti 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 { @Override 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 68a6fb68304..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,54 +97,6 @@ public void cleanUp() { callback = null; } - 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(); - } - @Test public void play() throws Exception { testOnCommandRequest(COMMAND_PLAY_PAUSE, RemoteMediaController::play); @@ -408,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 8006eb40121..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 @@ -84,10 +84,9 @@ public MediaSession.ConnectionResult onConnect( } @After - public void cleanUp() { - if (session != null) { - session.release(); - } + public void tearDown() throws Exception { + controller.release(); + session.release(); } @Test 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 11dad583acb..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; @@ -106,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; From c56c6a2ea4ec2e0bac2b75ced6f3c1f083abd934 Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 4 Mar 2022 17:52:46 +0000 Subject: [PATCH 18/20] Start playback from notification This change fixes two bugs where MediaSessionServe shows a notification with the Play icon but tapping it will not start playback: 1. After playback ends: we need to seek to the beginning of the media item. 2. After adding media items to the player but not starting playback: We need to call Player.prepare() too. PiperOrigin-RevId: 432469953 (cherry picked from commit 1023b9d55efe8f0470c5f3b75cd84e40860f79be) --- .../demo/session/PlayableFolderActivity.kt | 4 +++ .../media3/demo/session/PlaybackService.kt | 1 - .../DefaultMediaNotificationProvider.java | 18 +++++----- .../session/MediaNotificationManager.java | 4 ++- .../session/MediaSessionLegacyStub.java | 36 +++++++++++++++++-- 5 files changed, 50 insertions(+), 13 deletions(-) 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/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/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(); } }); } From dc83fae19d18ef3338bd824853beb13887a0af98 Mon Sep 17 00:00:00 2001 From: bachinger Date: Sat, 5 Mar 2022 14:16:43 +0000 Subject: [PATCH 19/20] Ignore MetadataRenderer when evaluating SSAI period transitions This makes the reading period advance early as expected at the end of an ad period. Before this change the reading position of the metadata renderer prevented advancing the period until metadata arrived after the start position of the following period. Only then the reading position of the metadata renderer is updated and beyond the start position of the following period which is a condition to advance the reading period. Because transitioning to the next period is a virtual transition and the SharedMediaPeriod keeps reading from the same underlying sample streams, the metadata renderer can safely be ignored for this check. #minor-release PiperOrigin-RevId: 432646037 (cherry picked from commit c7c75173224057defd031aa30bbf8346442700a6) --- .../java/androidx/media3/exoplayer/ExoPlayerImplInternal.java | 2 ++ 1 file changed, 2 insertions(+) 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()); } From 583249345a2782f308592131586d6bcb30e1aea9 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 9 Mar 2022 14:51:19 +0000 Subject: [PATCH 20/20] Version bump to exoplayer:2.17.1 and media3:1.0.0-alpha03 #minor-release PiperOrigin-RevId: 433467068 (cherry picked from commit af6f6bb40666d5c27fd4274ad5e3479cf593ce04) --- RELEASENOTES.md | 23 ++++++++++++++++++- constants.gradle | 4 ++-- .../media3/common/MediaLibraryInfo.java | 6 ++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fa6d929e698..204a19e7f6c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,27 @@ # 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). 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/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;