diff --git a/app/src/main/assets/native-10.6/nativeshell.js b/app/src/main/assets/native-10.6/nativeshell.js index bb3c4b8322..0cc022e9e7 100644 --- a/app/src/main/assets/native-10.6/nativeshell.js +++ b/app/src/main/assets/native-10.6/nativeshell.js @@ -57,7 +57,7 @@ window.NativeShell = { }, getPlugins() { - return ["native/exoplayer", "native/externalplayer"]; + return ["native/navigation", "native/exoplayer", "native/externalplayer"]; }, execCast(action, args, callback) { diff --git a/app/src/main/assets/native-10.6/navigation.js b/app/src/main/assets/native-10.6/navigation.js new file mode 100644 index 0000000000..5a291d6a88 --- /dev/null +++ b/app/src/main/assets/native-10.6/navigation.js @@ -0,0 +1,13 @@ +define(['inputManager', 'playbackManager'], function (inputManager, playbackManager) { + "use strict"; + + return function () { + window.NavigationHelper = this; + + this.goBack = function () { + inputManager.trigger('back'); + }; + + this.playbackManager = playbackManager; + }; +}); diff --git a/app/src/main/assets/native-10.7/NavigationPlugin.js b/app/src/main/assets/native-10.7/NavigationPlugin.js new file mode 100644 index 0000000000..fd30607b52 --- /dev/null +++ b/app/src/main/assets/native-10.7/NavigationPlugin.js @@ -0,0 +1,11 @@ +export class NavigationPlugin { + constructor({ playbackManager }) { + window['NavigationHelper'] = this; + + this.playbackManager = playbackManager; + } + + goBack() { + // TODO fix back action for 10.7 + }; +} diff --git a/app/src/main/assets/native-10.7/nativeshell.js b/app/src/main/assets/native-10.7/nativeshell.js index bfee16aa2c..5dfceb3499 100644 --- a/app/src/main/assets/native-10.7/nativeshell.js +++ b/app/src/main/assets/native-10.7/nativeshell.js @@ -15,6 +15,7 @@ const features = [ ]; const plugins = [ + 'NavigationPlugin', 'ExoPlayerPlugin', 'ExternalPlayerPlugin' ]; diff --git a/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt b/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt index 16896d4b96..87ad1af60b 100644 --- a/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt +++ b/app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt @@ -66,7 +66,7 @@ class WebViewFragment : Fragment() { serverId = requireNotNull(args.getLong(FRAGMENT_WEB_VIEW_EXTRA_SERVER_ID)) { "Server id has not been supplied!" } instanceUrl = requireNotNull(args.getString(FRAGMENT_WEB_VIEW_EXTRA_URL)) { "Server url has not been supplied!" } requireActivity().onBackPressedDispatcher.addCallback(this) { - if (!connected || !webappFunctionChannel.triggerInputManagerAction(Constants.INPUT_MANAGER_COMMAND_BACK)) { + if (!connected || !webappFunctionChannel.goBack()) { isEnabled = false activity?.onBackPressed() } @@ -131,6 +131,7 @@ class WebViewFragment : Fragment() { val path = url.path?.toLowerCase(Locale.ROOT) ?: return null return when { path.endsWith(Constants.APPLOADER_PATH) || path.endsWith(Constants.MAIN_BUNDLE_PATH) -> { + Timber.d("Loading bundle or apploader") assetsVersion = when { path.endsWith(Constants.APPLOADER_PATH) -> "10.6" path.endsWith(Constants.MAIN_BUNDLE_PATH) -> "10.7" diff --git a/app/src/main/java/org/jellyfin/mobile/utils/Constants.kt b/app/src/main/java/org/jellyfin/mobile/utils/Constants.kt index 259b07085d..2ac932b135 100644 --- a/app/src/main/java/org/jellyfin/mobile/utils/Constants.kt +++ b/app/src/main/java/org/jellyfin/mobile/utils/Constants.kt @@ -31,16 +31,15 @@ object Constants { const val PREF_EXTERNAL_PLAYER_APP = "pref_external_player_app" // InputManager commands - const val INPUT_MANAGER_COMMAND_PLAY_PAUSE = "playpause" - const val INPUT_MANAGER_COMMAND_PAUSE = "pause" - const val INPUT_MANAGER_COMMAND_PREVIOUS = "previous" - const val INPUT_MANAGER_COMMAND_NEXT = "next" - const val INPUT_MANAGER_COMMAND_REWIND = "rewind" - const val INPUT_MANAGER_COMMAND_FAST_FORWARD = "fastforward" - const val INPUT_MANAGER_COMMAND_STOP = "stop" - const val INPUT_MANAGER_COMMAND_VOL_UP = "volumeup" - const val INPUT_MANAGER_COMMAND_VOL_DOWN = "volumedown" - const val INPUT_MANAGER_COMMAND_BACK = "back" + const val PLAYBACK_MANAGER_COMMAND_PLAY = "unpause" + const val PLAYBACK_MANAGER_COMMAND_PAUSE = "pause" + const val PLAYBACK_MANAGER_COMMAND_PREVIOUS = "previousTrack" + const val PLAYBACK_MANAGER_COMMAND_NEXT = "nextTrack" + const val PLAYBACK_MANAGER_COMMAND_REWIND = "rewind" + const val PLAYBACK_MANAGER_COMMAND_FAST_FORWARD = "fastForward" + const val PLAYBACK_MANAGER_COMMAND_STOP = "stop" + const val PLAYBACK_MANAGER_COMMAND_VOL_UP = "volumeUp" + const val PLAYBACK_MANAGER_COMMAND_VOL_DOWN = "volumeDown" // Notification const val MEDIA_NOTIFICATION_CHANNEL_ID = "org.jellyfin.mobile.media.NOW_PLAYING" diff --git a/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt b/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt index e154a83c95..bec5eabe75 100644 --- a/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt +++ b/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt @@ -47,15 +47,15 @@ import org.jellyfin.mobile.utils.Constants.EXTRA_ITEM_ID import org.jellyfin.mobile.utils.Constants.EXTRA_PLAYER_ACTION import org.jellyfin.mobile.utils.Constants.EXTRA_POSITION import org.jellyfin.mobile.utils.Constants.EXTRA_TITLE -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_FAST_FORWARD -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_NEXT -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_PAUSE -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_PLAY_PAUSE -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_PREVIOUS -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_REWIND -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_STOP +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_FAST_FORWARD +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_NEXT +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_PAUSE +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_PREVIOUS +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_REWIND +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_STOP import org.jellyfin.mobile.utils.Constants.MEDIA_NOTIFICATION_CHANNEL_ID import org.jellyfin.mobile.utils.Constants.MEDIA_PLAYER_NOTIFICATION_ID +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_PLAY import org.jellyfin.mobile.utils.Constants.SUPPORTED_MUSIC_PLAYER_PLAYBACK_ACTIONS import org.jellyfin.mobile.utils.applyDefaultLocalAudioAttributes import org.jellyfin.mobile.utils.createMediaNotificationChannel @@ -85,29 +85,20 @@ class RemotePlayerService : Service(), CoroutineScope { val playbackState: PlaybackState? get() = mediaSession?.controller?.playbackState - /** - * only trip this flag if the user switches from headphones to speaker - * prevent stopping music when inserting headphones for the first time - */ - private var headphoneFlag = false private val receiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { AudioManager.ACTION_HEADSET_PLUG -> { - val state = intent.getIntExtra("state", 2) - if (state == 0) { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_PLAY_PAUSE) - headphoneFlag = true - } else if (headphoneFlag) { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_PLAY_PAUSE) - } + val state = intent.getIntExtra("state", 0) + // Pause playback when unplugging headphones + if (state == 0) webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_PAUSE) } BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED -> { val extras = intent.extras ?: return val state = extras.getInt(BluetoothA2dp.EXTRA_STATE) val previousState = extras.getInt(BluetoothA2dp.EXTRA_PREVIOUS_STATE) if ((state == BluetoothA2dp.STATE_DISCONNECTED || state == BluetoothA2dp.STATE_DISCONNECTING) && previousState == BluetoothA2dp.STATE_CONNECTED) { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_PAUSE) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_PAUSE) } } BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED -> { @@ -115,7 +106,7 @@ class RemotePlayerService : Service(), CoroutineScope { val state = extras.getInt(BluetoothHeadset.EXTRA_STATE) val previousState = extras.getInt(BluetoothHeadset.EXTRA_PREVIOUS_STATE) if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED && previousState == BluetoothHeadset.STATE_AUDIO_CONNECTED) { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_PAUSE) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_PAUSE) } } } @@ -145,7 +136,7 @@ class RemotePlayerService : Service(), CoroutineScope { createMediaNotificationChannel(notificationManager) } - override fun onBind(intent: Intent): IBinder? { + override fun onBind(intent: Intent): IBinder { return binder } @@ -346,31 +337,31 @@ class RemotePlayerService : Service(), CoroutineScope { setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS or MediaSession.FLAG_HANDLES_MEDIA_BUTTONS) setCallback(object : MediaSession.Callback() { override fun onPlay() { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_PLAY_PAUSE) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_PLAY) } override fun onPause() { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_PLAY_PAUSE) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_PAUSE) } override fun onSkipToPrevious() { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_PREVIOUS) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_PREVIOUS) } override fun onSkipToNext() { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_NEXT) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_NEXT) } override fun onRewind() { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_REWIND) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_REWIND) } override fun onFastForward() { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_FAST_FORWARD) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_FAST_FORWARD) } override fun onStop() { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_STOP) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_STOP) onStopped() } @@ -390,7 +381,6 @@ class RemotePlayerService : Service(), CoroutineScope { private fun onStopped() { notificationManager.cancel(MEDIA_PLAYER_NOTIFICATION_ID) mediaSession?.isActive = false - headphoneFlag = false stopWakelock() stopSelf() } diff --git a/app/src/main/java/org/jellyfin/mobile/webapp/RemoteVolumeProvider.kt b/app/src/main/java/org/jellyfin/mobile/webapp/RemoteVolumeProvider.kt index 27ad471671..442761037a 100644 --- a/app/src/main/java/org/jellyfin/mobile/webapp/RemoteVolumeProvider.kt +++ b/app/src/main/java/org/jellyfin/mobile/webapp/RemoteVolumeProvider.kt @@ -2,8 +2,8 @@ package org.jellyfin.mobile.webapp import android.media.AudioManager import android.media.VolumeProvider -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_VOL_DOWN -import org.jellyfin.mobile.utils.Constants.INPUT_MANAGER_COMMAND_VOL_UP +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_VOL_DOWN +import org.jellyfin.mobile.utils.Constants.PLAYBACK_MANAGER_COMMAND_VOL_UP class RemoteVolumeProvider( private val webappFunctionChannel: WebappFunctionChannel @@ -11,11 +11,11 @@ class RemoteVolumeProvider( override fun onAdjustVolume(direction: Int) { when (direction) { AudioManager.ADJUST_RAISE -> { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_VOL_UP) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_VOL_UP) currentVolume += 2 // TODO: have web notify app with new volume instead } AudioManager.ADJUST_LOWER -> { - webappFunctionChannel.triggerInputManagerAction(INPUT_MANAGER_COMMAND_VOL_DOWN) + webappFunctionChannel.callPlaybackManager(PLAYBACK_MANAGER_COMMAND_VOL_DOWN) currentVolume -= 2 // TODO: have web notify app with new volume instead } } diff --git a/app/src/main/java/org/jellyfin/mobile/webapp/WebappFunctionChannel.kt b/app/src/main/java/org/jellyfin/mobile/webapp/WebappFunctionChannel.kt index cbb6ebe995..3052dfa32a 100644 --- a/app/src/main/java/org/jellyfin/mobile/webapp/WebappFunctionChannel.kt +++ b/app/src/main/java/org/jellyfin/mobile/webapp/WebappFunctionChannel.kt @@ -14,10 +14,10 @@ class WebappFunctionChannel { fun call(action: String) = internalChannel.offer(action) // Web component helpers - private fun callWebComponent(component: String, cmd: String) = call("require(['$component'], function($component){$component.$cmd;});") - fun triggerInputManagerAction(action: String) = callWebComponent("inputManager", "trigger('$action')") - fun seekTo(pos: Long) = callWebComponent("inputManager", "trigger('seek', $pos)") - fun setVolume(volume: Int) = callWebComponent("playbackManager", "sendCommand({Name: 'SetVolume', Arguments: {Volume: $volume}})") + fun callPlaybackManager(action: String) = call("window.NavigationHelper.playbackManager.$action();") + fun setVolume(volume: Int) = callPlaybackManager("sendCommand({Name: 'SetVolume', Arguments: {Volume: $volume}})") + fun seekTo(pos: Long) = callPlaybackManager("seekMs($pos)") + fun goBack() = call("window.NavigationHelper.goBack();") // ExoPlayer helpers fun exoPlayerNotifyStopped() = call("window.ExoPlayer.notifyStopped()")