diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fd50e7bdbbb..7acd4b5ce80 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -39,6 +39,8 @@ `Format.copyWith*` methods. * Split `Format.bitrate` into `Format.averageBitrate` and `Format.peakBitrate` ([#2863](https://github.com/google/ExoPlayer/issues/2863)). + * Add optional automatic `WifiLock` handling to `SimpleExoPlayer` + ([#6914](https://github.com/google/ExoPlayer/issues/6914)). * Text: * Parse `` and `` tags in WebVTT subtitles (rendering is coming later). diff --git a/library/common/src/main/java/com/google/android/exoplayer2/C.java b/library/common/src/main/java/com/google/android/exoplayer2/C.java index 31a61148079..780d825d2db 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/C.java @@ -961,6 +961,37 @@ private C() {} /** Network type for other connections which are not Wifi or cellular (e.g. VPN, Bluetooth). */ public static final int NETWORK_TYPE_OTHER = 8; + /** + * Mode specifying whether the player should hold a WakeLock and a WifiLock. One of {@link + * #WAKE_MODE_NONE}, {@link #WAKE_MODE_LOCAL} and {@link #WAKE_MODE_NETWORK}. + */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({WAKE_MODE_NONE, WAKE_MODE_LOCAL, WAKE_MODE_NETWORK}) + public @interface WakeMode {} + /** + * A wake mode that will not cause the player to hold any locks. + * + *

This is suitable for applications that do not play media with the screen off. + */ + public static final int WAKE_MODE_NONE = 0; + /** + * A wake mode that will cause the player to hold a {@link android.os.PowerManager.WakeLock} + * during playback. + * + *

This is suitable for applications that play media with the screen off and do not load media + * over wifi. + */ + public static final int WAKE_MODE_LOCAL = 1; + /** + * A wake mode that will cause the player to hold a {@link android.os.PowerManager.WakeLock} and a + * {@link android.net.wifi.WifiManager.WifiLock} during playback. + * + *

This is suitable for applications that play media with the screen off and may load media + * over wifi. + */ + public static final int WAKE_MODE_NETWORK = 2; + /** * Track role flags. Possible flag values are {@link #ROLE_FLAG_MAIN}, {@link * #ROLE_FLAG_ALTERNATE}, {@link #ROLE_FLAG_SUPPLEMENTARY}, {@link #ROLE_FLAG_COMMENTARY}, {@link diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index a91e22b1241..eb2682defb8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -317,6 +317,7 @@ public SimpleExoPlayer build() { private final AudioBecomingNoisyManager audioBecomingNoisyManager; private final AudioFocusManager audioFocusManager; private final WakeLockManager wakeLockManager; + private final WifiLockManager wifiLockManager; @Nullable private Format videoFormat; @Nullable private Format audioFormat; @@ -432,6 +433,7 @@ protected SimpleExoPlayer( new AudioBecomingNoisyManager(context, eventHandler, componentListener); audioFocusManager = new AudioFocusManager(context, eventHandler, componentListener); wakeLockManager = new WakeLockManager(context); + wifiLockManager = new WifiLockManager(context); } @Override @@ -1395,6 +1397,7 @@ public void release() { audioBecomingNoisyManager.setEnabled(false); audioFocusManager.handleStop(); wakeLockManager.setStayAwake(false); + wifiLockManager.setStayAwake(false); player.release(); removeSurfaceCallbacks(); if (surface != null) { @@ -1529,9 +1532,45 @@ public long getContentBufferedPosition() { * * @param handleWakeLock Whether the player should use a {@link android.os.PowerManager.WakeLock} * to ensure the device stays awake for playback, even when the screen is off. + * @deprecated Use {@link #setWakeMode(int)} instead. */ + @Deprecated public void setHandleWakeLock(boolean handleWakeLock) { - wakeLockManager.setEnabled(handleWakeLock); + setWakeMode(handleWakeLock ? C.WAKE_MODE_LOCAL : C.WAKE_MODE_NONE); + } + + /** + * Sets how the player should keep the device awake for playback when the screen is off. + * + *

Enabling this feature requires the {@link android.Manifest.permission#WAKE_LOCK} permission. + * It should be used together with a foreground {@link android.app.Service} for use cases where + * playback occurs and the screen is off (e.g. background audio playback). It is not useful when + * the screen will be kept on during playback (e.g. foreground video playback). + * + *

When enabled, the locks ({@link android.os.PowerManager.WakeLock} / {@link + * android.net.wifi.WifiManager.WifiLock}) will be held whenever the player is in the {@link + * #STATE_READY} or {@link #STATE_BUFFERING} states with {@code playWhenReady = true}. The locks + * held depends on the specified {@link C.WakeMode}. + * + * @param wakeMode The {@link C.WakeMode} option to keep the device awake during playback. + */ + public void setWakeMode(@C.WakeMode int wakeMode) { + switch (wakeMode) { + case C.WAKE_MODE_NONE: + wakeLockManager.setEnabled(false); + wifiLockManager.setEnabled(false); + break; + case C.WAKE_MODE_LOCAL: + wakeLockManager.setEnabled(true); + wifiLockManager.setEnabled(false); + break; + case C.WAKE_MODE_NETWORK: + wakeLockManager.setEnabled(true); + wifiLockManager.setEnabled(true); + break; + default: + break; + } } // Internal methods. @@ -1644,16 +1683,18 @@ private void verifyApplicationThread() { } } - private void updateWakeLock() { + private void updateWakeAndWifiLock() { @State int playbackState = getPlaybackState(); switch (playbackState) { case Player.STATE_READY: case Player.STATE_BUFFERING: wakeLockManager.setStayAwake(getPlayWhenReady()); + wifiLockManager.setStayAwake(getPlayWhenReady()); break; case Player.STATE_ENDED: case Player.STATE_IDLE: wakeLockManager.setStayAwake(false); + wifiLockManager.setStayAwake(false); break; default: throw new IllegalStateException(); @@ -1951,13 +1992,13 @@ public void onIsLoadingChanged(boolean isLoading) { @Override public void onPlaybackStateChanged(@State int playbackState) { - updateWakeLock(); + updateWakeAndWifiLock(); } @Override public void onPlayWhenReadyChanged( boolean playWhenReady, @PlayWhenReadyChangeReason int reason) { - updateWakeLock(); + updateWakeAndWifiLock(); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java b/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java index 1e718136e87..bd385b0849d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/WakeLockManager.java @@ -39,7 +39,8 @@ private boolean stayAwake; public WakeLockManager(Context context) { - powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + powerManager = + (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE); } /** @@ -48,15 +49,15 @@ public WakeLockManager(Context context) { *

By default, wake lock handling is not enabled. Enabling this will acquire the wake lock if * necessary. Disabling this will release the wake lock if it is held. * - * @param enabled True if the player should handle a {@link WakeLock}, false otherwise. Please - * note that enabling this requires the {@link android.Manifest.permission#WAKE_LOCK} - * permission. + *

Enabling {@link WakeLock} requires the {@link android.Manifest.permission#WAKE_LOCK}. + * + * @param enabled True if the player should handle a {@link WakeLock}, false otherwise. */ public void setEnabled(boolean enabled) { if (enabled) { if (wakeLock == null) { if (powerManager == null) { - Log.w(TAG, "PowerManager was null, therefore the WakeLock was not created."); + Log.w(TAG, "PowerManager is null, therefore not creating the WakeLock."); return; } wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG); @@ -86,15 +87,16 @@ public void setStayAwake(boolean stayAwake) { // reasonable timeout that would not affect the user. @SuppressLint("WakelockTimeout") private void updateWakeLock() { - // Needed for the library nullness check. If enabled is true, the wakelock will not be null. - if (wakeLock != null) { - if (enabled && stayAwake) { - if (!wakeLock.isHeld()) { - wakeLock.acquire(); - } - } else if (wakeLock.isHeld()) { - wakeLock.release(); + if (wakeLock == null) { + return; + } + + if (enabled && stayAwake) { + if (!wakeLock.isHeld()) { + wakeLock.acquire(); } + } else if (wakeLock.isHeld()) { + wakeLock.release(); } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/WifiLockManager.java b/library/core/src/main/java/com/google/android/exoplayer2/WifiLockManager.java new file mode 100644 index 00000000000..f37ec6ab8b8 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/WifiLockManager.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiManager.WifiLock; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.util.Log; + +/** + * Handles a {@link WifiLock} + * + *

The handling of wifi locks requires the {@link android.Manifest.permission#WAKE_LOCK} + * permission. + */ +/* package */ final class WifiLockManager { + + private static final String TAG = "WifiLockManager"; + private static final String WIFI_LOCK_TAG = "ExoPlayer:WifiLockManager"; + + @Nullable private final WifiManager wifiManager; + @Nullable private WifiLock wifiLock; + private boolean enabled; + private boolean stayAwake; + + public WifiLockManager(Context context) { + wifiManager = + (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + } + + /** + * Sets whether to enable the usage of a {@link WifiLock}. + * + *

By default, wifi lock handling is not enabled. Enabling will acquire the wifi lock if + * necessary. Disabling will release the wifi lock if held. + * + *

Enabling {@link WifiLock} requires the {@link android.Manifest.permission#WAKE_LOCK}. + * + * @param enabled True if the player should handle a {@link WifiLock}. + */ + public void setEnabled(boolean enabled) { + if (enabled && wifiLock == null) { + if (wifiManager == null) { + Log.w(TAG, "WifiManager is null, therefore not creating the WifiLock."); + return; + } + wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, WIFI_LOCK_TAG); + } + + this.enabled = enabled; + updateWifiLock(); + } + + /** + * Sets whether to acquire or release the {@link WifiLock}. + * + *

The wifi lock will not be acquired unless handling has been enabled through {@link + * #setEnabled(boolean)}. + * + * @param stayAwake True if the player should acquire the {@link WifiLock}. False if it should + * release. + */ + public void setStayAwake(boolean stayAwake) { + this.stayAwake = stayAwake; + updateWifiLock(); + } + + private void updateWifiLock() { + if (wifiLock == null) { + return; + } + + if (enabled && stayAwake) { + if (!wifiLock.isHeld()) { + wifiLock.acquire(); + } + } else if (wifiLock.isHeld()) { + wifiLock.release(); + } + } +}