From 403e4404a7cce7fdd37f04bdf8d54feb649ecf5c Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Sun, 14 May 2023 13:32:25 -0700 Subject: [PATCH] Add local video playback --- app/build.gradle | 14 +-- .../java/gallery/memories/MainActivity.kt | 108 ++++++++++++++++-- app/src/main/java/gallery/memories/NativeX.kt | 30 ++++- app/src/main/res/layout/activity_main.xml | 10 +- 4 files changed, 144 insertions(+), 18 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f053c9d9..0d948ff6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,6 @@ android { targetSdk 33 versionCode 1 versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -32,16 +30,18 @@ android { } } +def mediaVersion = "1.0.1" + dependencies { implementation 'androidx.core:core-ktx:1.10.1' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.navigation:navigation-fragment:2.5.3' - implementation 'androidx.navigation:navigation-ui:2.5.3' + implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' + implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' implementation 'androidx.exifinterface:exifinterface:1.3.6' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation "androidx.media3:media3-exoplayer:$mediaVersion" + implementation "androidx.media3:media3-ui:$mediaVersion" + implementation "androidx.media3:media3-exoplayer-dash:$mediaVersion" } \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/MainActivity.kt b/app/src/main/java/gallery/memories/MainActivity.kt index 881471f3..5093e302 100644 --- a/app/src/main/java/gallery/memories/MainActivity.kt +++ b/app/src/main/java/gallery/memories/MainActivity.kt @@ -1,21 +1,36 @@ package gallery.memories import android.annotation.SuppressLint +import android.net.Uri import android.os.Bundle +import android.view.View import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import android.webkit.WebViewClient import androidx.appcompat.app.AppCompatActivity +import androidx.media3.common.MediaItem +import androidx.media3.common.util.UnstableApi +import androidx.media3.common.util.Util +import androidx.media3.exoplayer.ExoPlayer import gallery.memories.databinding.ActivityMainBinding -class MainActivity : AppCompatActivity() { - private lateinit var binding: ActivityMainBinding +@UnstableApi class MainActivity : AppCompatActivity() { + private val binding by lazy(LazyThreadSafetyMode.NONE) { + ActivityMainBinding.inflate(layoutInflater) + } + private lateinit var mNativeX: NativeX + private var player: ExoPlayer? = null + private var playerUri: Uri? = null + private var playerUid: String? = null + private var playWhenReady = true + private var mediaItemIndex = 0 + private var playbackPosition = 0L + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // Initialize services @@ -25,8 +40,35 @@ class MainActivity : AppCompatActivity() { initializeWebView() } - @SuppressLint("SetJavaScriptEnabled") + override fun onDestroy() { + super.onDestroy() + mNativeX.destroy() + } + + public override fun onResume() { + super.onResume() + if (playerUri != null && (Util.SDK_INT <= 23 || player == null)) { + initializePlayer(playerUri!!, playerUid!!) + } + } + + public override fun onPause() { + super.onPause() + if (Util.SDK_INT <= 23) { + releasePlayer() + } + } + + public override fun onStop() { + super.onStop() + if (Util.SDK_INT > 23) { + releasePlayer() + } + } + + @SuppressLint("SetJavaScriptEnabled", "ClickableViewAccessibility") private fun initializeWebView() { + // Intercept local APIs binding.webview.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { view.loadUrl(request.url.toString()) @@ -39,6 +81,15 @@ class MainActivity : AppCompatActivity() { } else null } } + + // Pass through touch events + binding.webview.setOnTouchListener { _, event -> + if (player != null) { + binding.videoView.dispatchTouchEvent(event) + } + false + } + val webSettings = binding.webview.settings webSettings.javaScriptEnabled = true webSettings.javaScriptCanOpenWindowsAutomatically = true @@ -49,11 +100,52 @@ class MainActivity : AppCompatActivity() { binding.webview.clearCache(true) binding.webview.addJavascriptInterface(mNativeX, "nativex") binding.webview.loadUrl("http://10.0.2.2:8035/index.php/apps/memories/") + binding.webview.setBackgroundColor(0x00000000) } - // Cleanup - override fun onDestroy() { - super.onDestroy() - mNativeX.destroy() + fun initializePlayer(uri: Uri, uid: String) { + if (player != null) { + if (playerUid.equals(uid)) return + player?.release() + player = null + } + + playerUri = uri + playerUid = uid + + player = ExoPlayer.Builder(this) + .build() + .also { exoPlayer -> + binding.videoView.player = exoPlayer + binding.videoView.visibility = View.VISIBLE + val mediaItem = MediaItem.fromUri(uri) + exoPlayer.setMediaItems(listOf(mediaItem), mediaItemIndex, playbackPosition) + exoPlayer.playWhenReady = playWhenReady + exoPlayer.prepare() + } + } + + fun destroyPlayer(uid: String) { + if (playerUid.equals(uid)) { + releasePlayer() + + // Reset vars + playWhenReady = true + mediaItemIndex = 0 + playbackPosition = 0L + playerUri = null + playerUid = null + } + } + + private fun releasePlayer() { + player?.let { exoPlayer -> + playbackPosition = exoPlayer.currentPosition + mediaItemIndex = exoPlayer.currentMediaItemIndex + playWhenReady = exoPlayer.playWhenReady + exoPlayer.release() + } + player = null + binding.videoView.visibility = View.GONE } } \ No newline at end of file diff --git a/app/src/main/java/gallery/memories/NativeX.kt b/app/src/main/java/gallery/memories/NativeX.kt index 7ca1813b..fa951b1f 100644 --- a/app/src/main/java/gallery/memories/NativeX.kt +++ b/app/src/main/java/gallery/memories/NativeX.kt @@ -9,14 +9,15 @@ import android.view.WindowInsetsController import android.webkit.JavascriptInterface import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse -import androidx.appcompat.app.AppCompatActivity +import androidx.media3.common.util.UnstableApi +import gallery.memories.mapper.SystemImage import gallery.memories.service.DownloadService import gallery.memories.service.ImageService import gallery.memories.service.TimelineQuery import java.io.ByteArrayInputStream import java.net.URLDecoder -class NativeX(private val mActivity: AppCompatActivity) { +@UnstableApi class NativeX(private val mActivity: MainActivity) { val TAG = "NativeX" private val mImageService: ImageService = ImageService(mActivity) @@ -99,6 +100,31 @@ class NativeX(private val mActivity: AppCompatActivity) { mDlService!!.queue(url, filename) } + @JavascriptInterface + fun playVideoLocal(fileId: String?) { + if (fileId == null) return; + + Thread { + // Get URI of local video + val videos = SystemImage.getByIds(mActivity, arrayListOf(fileId.toLong())) + if (videos.isEmpty()) return@Thread + val video = videos[0] + + // Play with exoplayer + mActivity.runOnUiThread { + mActivity.initializePlayer(video.uri, fileId) + } + }.start() + } + + @JavascriptInterface + fun destroyVideo(fileId: String?) { + if (fileId == null) return; + mActivity.runOnUiThread { + mActivity.destroyPlayer(fileId) + } + } + @Throws(Exception::class) private fun routerGet(path: String): WebResourceResponse { val parts = path.split("/").toTypedArray() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b5f398bf..5f2a4f07 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,10 +6,18 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + + /> \ No newline at end of file