mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-06-27 23:52:53 +00:00
Merge pull request #11829 from Profpatsch/PlayerUIList-to-kotlin
Player UI list to kotlin
This commit is contained in:
commit
d1954baf29
@ -246,7 +246,7 @@ public final class VideoDetailFragment
|
|||||||
// It will do nothing if the player is not in fullscreen mode
|
// It will do nothing if the player is not in fullscreen mode
|
||||||
hideSystemUiIfNeeded();
|
hideSystemUiIfNeeded();
|
||||||
|
|
||||||
final Optional<MainPlayerUi> playerUi = player.UIs().get(MainPlayerUi.class);
|
final Optional<MainPlayerUi> playerUi = player.UIs().getOpt(MainPlayerUi.class);
|
||||||
if (!player.videoPlayerSelected() && !playAfterConnect) {
|
if (!player.videoPlayerSelected() && !playAfterConnect) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -529,7 +529,7 @@ public final class VideoDetailFragment
|
|||||||
binding.overlayPlayPauseButton.setOnClickListener(v -> {
|
binding.overlayPlayPauseButton.setOnClickListener(v -> {
|
||||||
if (playerIsNotStopped()) {
|
if (playerIsNotStopped()) {
|
||||||
player.playPause();
|
player.playPause();
|
||||||
player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0));
|
player.UIs().getOpt(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0));
|
||||||
showSystemUi();
|
showSystemUi();
|
||||||
} else {
|
} else {
|
||||||
autoPlayEnabled = true; // forcefully start playing
|
autoPlayEnabled = true; // forcefully start playing
|
||||||
@ -688,7 +688,7 @@ public final class VideoDetailFragment
|
|||||||
@Override
|
@Override
|
||||||
public boolean onKeyDown(final int keyCode) {
|
public boolean onKeyDown(final int keyCode) {
|
||||||
return isPlayerAvailable()
|
return isPlayerAvailable()
|
||||||
&& player.UIs().get(VideoPlayerUi.class)
|
&& player.UIs().getOpt(VideoPlayerUi.class)
|
||||||
.map(playerUi -> playerUi.onKeyDown(keyCode)).orElse(false);
|
.map(playerUi -> playerUi.onKeyDown(keyCode)).orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1028,7 +1028,7 @@ public final class VideoDetailFragment
|
|||||||
// If a user watched video inside fullscreen mode and than chose another player
|
// If a user watched video inside fullscreen mode and than chose another player
|
||||||
// return to non-fullscreen mode
|
// return to non-fullscreen mode
|
||||||
if (isPlayerAvailable()) {
|
if (isPlayerAvailable()) {
|
||||||
player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> {
|
player.UIs().getOpt(MainPlayerUi.class).ifPresent(playerUi -> {
|
||||||
if (playerUi.isFullscreen()) {
|
if (playerUi.isFullscreen()) {
|
||||||
playerUi.toggleFullscreen();
|
playerUi.toggleFullscreen();
|
||||||
}
|
}
|
||||||
@ -1244,7 +1244,7 @@ public final class VideoDetailFragment
|
|||||||
// setup the surface view height, so that it fits the video correctly
|
// setup the surface view height, so that it fits the video correctly
|
||||||
setHeightThumbnail();
|
setHeightThumbnail();
|
||||||
|
|
||||||
player.UIs().get(MainPlayerUi.class).ifPresent(playerUi -> {
|
player.UIs().getOpt(MainPlayerUi.class).ifPresent(playerUi -> {
|
||||||
// sometimes binding would be null here, even though getView() != null above u.u
|
// sometimes binding would be null here, even though getView() != null above u.u
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
// prevent from re-adding a view multiple times
|
// prevent from re-adding a view multiple times
|
||||||
@ -1260,7 +1260,7 @@ public final class VideoDetailFragment
|
|||||||
makeDefaultHeightForVideoPlaceholder();
|
makeDefaultHeightForVideoPlaceholder();
|
||||||
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.UIs().get(VideoPlayerUi.class).ifPresent(VideoPlayerUi::removeViewFromParent);
|
player.UIs().getOpt(VideoPlayerUi.class).ifPresent(VideoPlayerUi::removeViewFromParent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1327,7 +1327,7 @@ public final class VideoDetailFragment
|
|||||||
binding.detailThumbnailImageView.setMinimumHeight(newHeight);
|
binding.detailThumbnailImageView.setMinimumHeight(newHeight);
|
||||||
if (isPlayerAvailable()) {
|
if (isPlayerAvailable()) {
|
||||||
final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT);
|
final int maxHeight = (int) (metrics.heightPixels * MAX_PLAYER_HEIGHT);
|
||||||
player.UIs().get(VideoPlayerUi.class).ifPresent(ui ->
|
player.UIs().getOpt(VideoPlayerUi.class).ifPresent(ui ->
|
||||||
ui.getBinding().surfaceView.setHeights(newHeight,
|
ui.getBinding().surfaceView.setHeights(newHeight,
|
||||||
ui.isFullscreen() ? newHeight : maxHeight));
|
ui.isFullscreen() ? newHeight : maxHeight));
|
||||||
}
|
}
|
||||||
@ -1861,7 +1861,7 @@ public final class VideoDetailFragment
|
|||||||
public void onFullscreenStateChanged(final boolean fullscreen) {
|
public void onFullscreenStateChanged(final boolean fullscreen) {
|
||||||
setupBrightness();
|
setupBrightness();
|
||||||
if (!isPlayerAndPlayerServiceAvailable()
|
if (!isPlayerAndPlayerServiceAvailable()
|
||||||
|| player.UIs().get(MainPlayerUi.class).isEmpty()
|
|| player.UIs().getOpt(MainPlayerUi.class).isEmpty()
|
||||||
|| getRoot().map(View::getParent).isEmpty()) {
|
|| getRoot().map(View::getParent).isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1890,7 +1890,7 @@ public final class VideoDetailFragment
|
|||||||
final boolean isLandscape = DeviceUtils.isLandscape(requireContext());
|
final boolean isLandscape = DeviceUtils.isLandscape(requireContext());
|
||||||
if (DeviceUtils.isTablet(activity)
|
if (DeviceUtils.isTablet(activity)
|
||||||
&& (!globalScreenOrientationLocked(activity) || isLandscape)) {
|
&& (!globalScreenOrientationLocked(activity) || isLandscape)) {
|
||||||
player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::toggleFullscreen);
|
player.UIs().getOpt(MainPlayerUi.class).ifPresent(MainPlayerUi::toggleFullscreen);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1990,7 +1990,7 @@ public final class VideoDetailFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFullscreen() {
|
private boolean isFullscreen() {
|
||||||
return isPlayerAvailable() && player.UIs().get(VideoPlayerUi.class)
|
return isPlayerAvailable() && player.UIs().getOpt(VideoPlayerUi.class)
|
||||||
.map(VideoPlayerUi::isFullscreen).orElse(false);
|
.map(VideoPlayerUi::isFullscreen).orElse(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2067,7 +2067,7 @@ public final class VideoDetailFragment
|
|||||||
setAutoPlay(true);
|
setAutoPlay(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
player.UIs().get(MainPlayerUi.class).ifPresent(MainPlayerUi::checkLandscape);
|
player.UIs().getOpt(MainPlayerUi.class).ifPresent(MainPlayerUi::checkLandscape);
|
||||||
// Let's give a user time to look at video information page if video is not playing
|
// Let's give a user time to look at video information page if video is not playing
|
||||||
if (globalScreenOrientationLocked(activity) && !player.isPlaying()) {
|
if (globalScreenOrientationLocked(activity) && !player.isPlaying()) {
|
||||||
player.play();
|
player.play();
|
||||||
@ -2332,7 +2332,7 @@ public final class VideoDetailFragment
|
|||||||
&& player.isPlaying()
|
&& player.isPlaying()
|
||||||
&& !isFullscreen()
|
&& !isFullscreen()
|
||||||
&& !DeviceUtils.isTablet(activity)) {
|
&& !DeviceUtils.isTablet(activity)) {
|
||||||
player.UIs().get(MainPlayerUi.class)
|
player.UIs().getOpt(MainPlayerUi.class)
|
||||||
.ifPresent(MainPlayerUi::toggleFullscreen);
|
.ifPresent(MainPlayerUi::toggleFullscreen);
|
||||||
}
|
}
|
||||||
setOverlayLook(binding.appBarLayout, behavior, 1);
|
setOverlayLook(binding.appBarLayout, behavior, 1);
|
||||||
@ -2346,7 +2346,7 @@ public final class VideoDetailFragment
|
|||||||
// Re-enable clicks
|
// Re-enable clicks
|
||||||
setOverlayElementsClickable(true);
|
setOverlayElementsClickable(true);
|
||||||
if (isPlayerAvailable()) {
|
if (isPlayerAvailable()) {
|
||||||
player.UIs().get(MainPlayerUi.class)
|
player.UIs().getOpt(MainPlayerUi.class)
|
||||||
.ifPresent(MainPlayerUi::closeItemsList);
|
.ifPresent(MainPlayerUi::closeItemsList);
|
||||||
}
|
}
|
||||||
setOverlayLook(binding.appBarLayout, behavior, 0);
|
setOverlayLook(binding.appBarLayout, behavior, 0);
|
||||||
@ -2357,7 +2357,7 @@ public final class VideoDetailFragment
|
|||||||
showSystemUi();
|
showSystemUi();
|
||||||
}
|
}
|
||||||
if (isPlayerAvailable()) {
|
if (isPlayerAvailable()) {
|
||||||
player.UIs().get(MainPlayerUi.class).ifPresent(ui -> {
|
player.UIs().getOpt(MainPlayerUi.class).ifPresent(ui -> {
|
||||||
if (ui.isControlsVisible()) {
|
if (ui.isControlsVisible()) {
|
||||||
ui.hideControls(0, 0);
|
ui.hideControls(0, 0);
|
||||||
}
|
}
|
||||||
@ -2454,7 +2454,7 @@ public final class VideoDetailFragment
|
|||||||
|
|
||||||
public Optional<View> getRoot() {
|
public Optional<View> getRoot() {
|
||||||
return Optional.ofNullable(player)
|
return Optional.ofNullable(player)
|
||||||
.flatMap(player1 -> player1.UIs().get(VideoPlayerUi.class))
|
.flatMap(player1 -> player1.UIs().getOpt(VideoPlayerUi.class))
|
||||||
.map(playerUi -> playerUi.getBinding().getRoot());
|
.map(playerUi -> playerUi.getBinding().getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,14 +473,15 @@ public final class Player implements PlaybackListener, Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initUIsForCurrentPlayerType() {
|
private void initUIsForCurrentPlayerType() {
|
||||||
if ((UIs.get(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN)
|
if ((UIs.getOpt(MainPlayerUi.class).isPresent() && playerType == PlayerType.MAIN)
|
||||||
|| (UIs.get(PopupPlayerUi.class).isPresent() && playerType == PlayerType.POPUP)) {
|
|| (UIs.getOpt(PopupPlayerUi.class).isPresent()
|
||||||
|
&& playerType == PlayerType.POPUP)) {
|
||||||
// correct UI already in place
|
// correct UI already in place
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to reuse binding if possible
|
// try to reuse binding if possible
|
||||||
final PlayerBinding binding = UIs.get(VideoPlayerUi.class).map(VideoPlayerUi::getBinding)
|
final PlayerBinding binding = UIs.getOpt(VideoPlayerUi.class).map(VideoPlayerUi::getBinding)
|
||||||
.orElseGet(() -> {
|
.orElseGet(() -> {
|
||||||
if (playerType == PlayerType.AUDIO) {
|
if (playerType == PlayerType.AUDIO) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -148,7 +148,7 @@ public final class PlayerService extends MediaBrowserServiceCompat {
|
|||||||
// no one already and starting the service in foreground should not create any issues.
|
// no one already and starting the service in foreground should not create any issues.
|
||||||
// If the service is already started in foreground, requesting it to be started
|
// If the service is already started in foreground, requesting it to be started
|
||||||
// shouldn't do anything.
|
// shouldn't do anything.
|
||||||
player.UIs().get(NotificationPlayerUi.class)
|
player.UIs().getOpt(NotificationPlayerUi.class)
|
||||||
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
|
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
|
||||||
|
|
||||||
if (playerWasNull && onPlayerStartedOrStopped != null) {
|
if (playerWasNull && onPlayerStartedOrStopped != null) {
|
||||||
@ -173,7 +173,7 @@ public final class PlayerService extends MediaBrowserServiceCompat {
|
|||||||
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.handleIntent(intent);
|
player.handleIntent(intent);
|
||||||
player.UIs().get(MediaSessionPlayerUi.class)
|
player.UIs().getOpt(MediaSessionPlayerUi.class)
|
||||||
.ifPresent(ui -> ui.handleMediaButtonIntent(intent));
|
.ifPresent(ui -> ui.handleMediaButtonIntent(intent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ public class MediaSessionPlayerUi extends PlayerUi
|
|||||||
public void play() {
|
public void play() {
|
||||||
player.play();
|
player.play();
|
||||||
// hide the player controls even if the play command came from the media session
|
// hide the player controls even if the play command came from the media session
|
||||||
player.UIs().get(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0));
|
player.UIs().getOpt(VideoPlayerUi.class).ifPresent(ui -> ui.hideControls(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -102,7 +102,7 @@ public final class NotificationUtil {
|
|||||||
mediaStyle.setShowActionsInCompactView(compactSlots);
|
mediaStyle.setShowActionsInCompactView(compactSlots);
|
||||||
}
|
}
|
||||||
player.UIs()
|
player.UIs()
|
||||||
.get(MediaSessionPlayerUi.class)
|
.getOpt(MediaSessionPlayerUi.class)
|
||||||
.flatMap(MediaSessionPlayerUi::getSessionToken)
|
.flatMap(MediaSessionPlayerUi::getSessionToken)
|
||||||
.ifPresent(mediaStyle::setMediaSession);
|
.ifPresent(mediaStyle::setMediaSession);
|
||||||
|
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
package org.schabi.newpipe.player.ui;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public final class PlayerUiList {
|
|
||||||
final List<PlayerUi> playerUis = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link PlayerUiList} starting with the provided player uis. The provided player uis
|
|
||||||
* will not be prepared like those passed to {@link #addAndPrepare(PlayerUi)}, because when
|
|
||||||
* the {@link PlayerUiList} constructor is called, the player is still not running and it
|
|
||||||
* wouldn't make sense to initialize uis then. Instead the player will initialize them by doing
|
|
||||||
* proper calls to {@link #call(Consumer)}.
|
|
||||||
*
|
|
||||||
* @param initialPlayerUis the player uis this list should start with; the order will be kept
|
|
||||||
*/
|
|
||||||
public PlayerUiList(final PlayerUi... initialPlayerUis) {
|
|
||||||
playerUis.addAll(List.of(initialPlayerUis));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the provided player ui to the list and calls on it the initialization functions that
|
|
||||||
* apply based on the current player state. The preparation step needs to be done since when UIs
|
|
||||||
* are removed and re-added, the player will not call e.g. initPlayer again since the exoplayer
|
|
||||||
* is already initialized, but we need to notify the newly built UI that the player is ready
|
|
||||||
* nonetheless.
|
|
||||||
* @param playerUi the player ui to prepare and add to the list; its {@link
|
|
||||||
* PlayerUi#getPlayer()} will be used to query information about the player
|
|
||||||
* state
|
|
||||||
*/
|
|
||||||
public void addAndPrepare(final PlayerUi playerUi) {
|
|
||||||
if (playerUi.getPlayer().getFragmentListener().isPresent()) {
|
|
||||||
// make sure UIs know whether a service is connected or not
|
|
||||||
playerUi.onFragmentListenerSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!playerUi.getPlayer().exoPlayerIsNull()) {
|
|
||||||
playerUi.initPlayer();
|
|
||||||
if (playerUi.getPlayer().getPlayQueue() != null) {
|
|
||||||
playerUi.initPlayback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playerUis.add(playerUi);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys all matching player UIs and removes them from the list.
|
|
||||||
* @param playerUiType the class of the player UI to destroy; the {@link
|
|
||||||
* Class#isInstance(Object)} method will be used, so even subclasses will be
|
|
||||||
* destroyed and removed
|
|
||||||
* @param <T> the class type parameter
|
|
||||||
*/
|
|
||||||
public <T> void destroyAll(final Class<T> playerUiType) {
|
|
||||||
playerUis.stream()
|
|
||||||
.filter(playerUiType::isInstance)
|
|
||||||
.forEach(playerUi -> {
|
|
||||||
playerUi.destroyPlayer();
|
|
||||||
playerUi.destroy();
|
|
||||||
});
|
|
||||||
playerUis.removeIf(playerUiType::isInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param playerUiType the class of the player UI to return; the {@link
|
|
||||||
* Class#isInstance(Object)} method will be used, so even subclasses could
|
|
||||||
* be returned
|
|
||||||
* @param <T> the class type parameter
|
|
||||||
* @return the first player UI of the required type found in the list, or an empty {@link
|
|
||||||
* Optional} otherwise
|
|
||||||
*/
|
|
||||||
public <T> Optional<T> get(final Class<T> playerUiType) {
|
|
||||||
return playerUis.stream()
|
|
||||||
.filter(playerUiType::isInstance)
|
|
||||||
.map(playerUiType::cast)
|
|
||||||
.findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls the provided consumer on all player UIs in the list, in order of addition.
|
|
||||||
* @param consumer the consumer to call with player UIs
|
|
||||||
*/
|
|
||||||
public void call(final Consumer<PlayerUi> consumer) {
|
|
||||||
//noinspection SimplifyStreamApiCallChains
|
|
||||||
playerUis.stream().forEachOrdered(consumer);
|
|
||||||
}
|
|
||||||
}
|
|
124
app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.kt
Normal file
124
app/src/main/java/org/schabi/newpipe/player/ui/PlayerUiList.kt
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package org.schabi.newpipe.player.ui
|
||||||
|
|
||||||
|
import org.schabi.newpipe.util.GuardedByMutex
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
class PlayerUiList(vararg initialPlayerUis: PlayerUi) {
|
||||||
|
var playerUis = GuardedByMutex(mutableListOf<PlayerUi>())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a [PlayerUiList] starting with the provided player uis. The provided player uis
|
||||||
|
* will not be prepared like those passed to [.addAndPrepare], because when
|
||||||
|
* the [PlayerUiList] constructor is called, the player is still not running and it
|
||||||
|
* wouldn't make sense to initialize uis then. Instead the player will initialize them by doing
|
||||||
|
* proper calls to [.call].
|
||||||
|
*
|
||||||
|
* @param initialPlayerUis the player uis this list should start with; the order will be kept
|
||||||
|
*/
|
||||||
|
init {
|
||||||
|
playerUis.runWithLockSync {
|
||||||
|
lockData.addAll(listOf(*initialPlayerUis))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the provided player ui to the list and calls on it the initialization functions that
|
||||||
|
* apply based on the current player state. The preparation step needs to be done since when UIs
|
||||||
|
* are removed and re-added, the player will not call e.g. initPlayer again since the exoplayer
|
||||||
|
* is already initialized, but we need to notify the newly built UI that the player is ready
|
||||||
|
* nonetheless.
|
||||||
|
* @param playerUi the player ui to prepare and add to the list; its [PlayerUi.getPlayer]
|
||||||
|
* will be used to query information about the player state
|
||||||
|
*/
|
||||||
|
fun addAndPrepare(playerUi: PlayerUi) {
|
||||||
|
if (playerUi.getPlayer().fragmentListener.isPresent) {
|
||||||
|
// make sure UIs know whether a service is connected or not
|
||||||
|
playerUi.onFragmentListenerSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!playerUi.getPlayer().exoPlayerIsNull()) {
|
||||||
|
playerUi.initPlayer()
|
||||||
|
if (playerUi.getPlayer().playQueue != null) {
|
||||||
|
playerUi.initPlayback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playerUis.runWithLockSync {
|
||||||
|
lockData.add(playerUi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys all matching player UIs and removes them from the list.
|
||||||
|
* @param playerUiType the class of the player UI to destroy;
|
||||||
|
* the [Class.isInstance] method will be used, so even subclasses will be
|
||||||
|
* destroyed and removed
|
||||||
|
* @param T the class type parameter </T>
|
||||||
|
* */
|
||||||
|
fun <T> destroyAll(playerUiType: Class<T?>) {
|
||||||
|
val toDestroy = mutableListOf<PlayerUi>()
|
||||||
|
|
||||||
|
// short blocking removal from class to prevent interfering from other threads
|
||||||
|
playerUis.runWithLockSync {
|
||||||
|
val new = mutableListOf<PlayerUi>()
|
||||||
|
for (ui in lockData) {
|
||||||
|
if (playerUiType.isInstance(ui)) {
|
||||||
|
toDestroy.add(ui)
|
||||||
|
} else {
|
||||||
|
new.add(ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lockData = new
|
||||||
|
}
|
||||||
|
// then actually destroy the UIs
|
||||||
|
for (ui in toDestroy) {
|
||||||
|
ui.destroyPlayer()
|
||||||
|
ui.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param playerUiType the class of the player UI to return;
|
||||||
|
* the [Class.isInstance] method will be used, so even subclasses could be returned
|
||||||
|
* @param T the class type parameter
|
||||||
|
* @return the first player UI of the required type found in the list, or null
|
||||||
|
</T> */
|
||||||
|
fun <T> get(playerUiType: Class<T>): T? =
|
||||||
|
playerUis.runWithLockSync {
|
||||||
|
for (ui in lockData) {
|
||||||
|
if (playerUiType.isInstance(ui)) {
|
||||||
|
when (val r = playerUiType.cast(ui)) {
|
||||||
|
// try all UIs before returning null
|
||||||
|
null -> continue
|
||||||
|
else -> return@runWithLockSync r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@runWithLockSync null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param playerUiType the class of the player UI to return;
|
||||||
|
* the [Class.isInstance] method will be used, so even subclasses could be returned
|
||||||
|
* @param T the class type parameter
|
||||||
|
* @return the first player UI of the required type found in the list, or an empty
|
||||||
|
* [Optional] otherwise
|
||||||
|
</T> */
|
||||||
|
@Deprecated("use get", ReplaceWith("get(playerUiType)"))
|
||||||
|
fun <T> getOpt(playerUiType: Class<T>): Optional<T & Any> =
|
||||||
|
Optional.ofNullable(get(playerUiType))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the provided consumer on all player UIs in the list, in order of addition.
|
||||||
|
* @param consumer the consumer to call with player UIs
|
||||||
|
*/
|
||||||
|
fun call(consumer: java.util.function.Consumer<PlayerUi>) {
|
||||||
|
// copy the list out of the mutex before calling the consumer which might block
|
||||||
|
val new = playerUis.runWithLockSync {
|
||||||
|
lockData.toMutableList()
|
||||||
|
}
|
||||||
|
for (ui in new) {
|
||||||
|
consumer.accept(ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
app/src/main/java/org/schabi/newpipe/util/GuardedByMutex.kt
Normal file
47
app/src/main/java/org/schabi/newpipe/util/GuardedByMutex.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package org.schabi.newpipe.util
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
|
/** Guard the given data so that it can only be accessed by locking the mutex first.
|
||||||
|
*
|
||||||
|
* Inspired by [this blog post](https://jonnyzzz.com/blog/2017/03/01/guarded-by-lock/)
|
||||||
|
* */
|
||||||
|
class GuardedByMutex<T>(
|
||||||
|
private var data: T,
|
||||||
|
private val lock: Mutex = Mutex(locked = false),
|
||||||
|
) {
|
||||||
|
|
||||||
|
/** Lock the mutex and access the data, blocking the current thread.
|
||||||
|
* @param action to run with locked mutex
|
||||||
|
* */
|
||||||
|
fun <Y> runWithLockSync(
|
||||||
|
action: MutexData<T>.() -> Y
|
||||||
|
) =
|
||||||
|
runBlocking {
|
||||||
|
lock.withLock {
|
||||||
|
MutexData(data, { d -> data = d }).action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lock the mutex and access the data, suspending the coroutine.
|
||||||
|
* @param action to run with locked mutex
|
||||||
|
* */
|
||||||
|
suspend fun <Y> runWithLock(action: MutexData<T>.() -> Y) =
|
||||||
|
lock.withLock {
|
||||||
|
MutexData(data, { d -> data = d }).action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The data inside a [GuardedByMutex], which can be accessed via [lockData].
|
||||||
|
* [lockData] is a `var`, so you can `set` it as well.
|
||||||
|
* */
|
||||||
|
class MutexData<T>(data: T, val setFun: (T) -> Unit) {
|
||||||
|
/** The data inside this [GuardedByMutex] */
|
||||||
|
var lockData: T = data
|
||||||
|
set(t) {
|
||||||
|
setFun(t)
|
||||||
|
field = t
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user