Add local video playback

pull/653/merge
Varun Patil 2023-05-14 13:32:25 -07:00
parent ce3ee760d0
commit 403e4404a7
4 changed files with 144 additions and 18 deletions

View File

@ -13,8 +13,6 @@ android {
targetSdk 33 targetSdk 33
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@ -32,16 +30,18 @@ android {
} }
} }
def mediaVersion = "1.0.1"
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.core:core-ktx:1.10.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0' implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment:2.5.3' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
implementation 'androidx.exifinterface:exifinterface:1.3.6' implementation 'androidx.exifinterface:exifinterface:1.3.6'
testImplementation 'junit:junit:4.13.2' implementation "androidx.media3:media3-exoplayer:$mediaVersion"
androidTestImplementation 'androidx.test.ext:junit:1.1.5' implementation "androidx.media3:media3-ui:$mediaVersion"
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation "androidx.media3:media3-exoplayer-dash:$mediaVersion"
} }

View File

@ -1,21 +1,36 @@
package gallery.memories package gallery.memories
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity 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 import gallery.memories.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() { @UnstableApi class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private val binding by lazy(LazyThreadSafetyMode.NONE) {
ActivityMainBinding.inflate(layoutInflater)
}
private lateinit var mNativeX: NativeX 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
// Initialize services // Initialize services
@ -25,8 +40,35 @@ class MainActivity : AppCompatActivity() {
initializeWebView() 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() { private fun initializeWebView() {
// Intercept local APIs
binding.webview.webViewClient = object : WebViewClient() { binding.webview.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
view.loadUrl(request.url.toString()) view.loadUrl(request.url.toString())
@ -39,6 +81,15 @@ class MainActivity : AppCompatActivity() {
} else null } else null
} }
} }
// Pass through touch events
binding.webview.setOnTouchListener { _, event ->
if (player != null) {
binding.videoView.dispatchTouchEvent(event)
}
false
}
val webSettings = binding.webview.settings val webSettings = binding.webview.settings
webSettings.javaScriptEnabled = true webSettings.javaScriptEnabled = true
webSettings.javaScriptCanOpenWindowsAutomatically = true webSettings.javaScriptCanOpenWindowsAutomatically = true
@ -49,11 +100,52 @@ class MainActivity : AppCompatActivity() {
binding.webview.clearCache(true) binding.webview.clearCache(true)
binding.webview.addJavascriptInterface(mNativeX, "nativex") binding.webview.addJavascriptInterface(mNativeX, "nativex")
binding.webview.loadUrl("http://10.0.2.2:8035/index.php/apps/memories/") binding.webview.loadUrl("http://10.0.2.2:8035/index.php/apps/memories/")
binding.webview.setBackgroundColor(0x00000000)
} }
// Cleanup fun initializePlayer(uri: Uri, uid: String) {
override fun onDestroy() { if (player != null) {
super.onDestroy() if (playerUid.equals(uid)) return
mNativeX.destroy() 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
} }
} }

View File

@ -9,14 +9,15 @@ import android.view.WindowInsetsController
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse 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.DownloadService
import gallery.memories.service.ImageService import gallery.memories.service.ImageService
import gallery.memories.service.TimelineQuery import gallery.memories.service.TimelineQuery
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.net.URLDecoder import java.net.URLDecoder
class NativeX(private val mActivity: AppCompatActivity) { @UnstableApi class NativeX(private val mActivity: MainActivity) {
val TAG = "NativeX" val TAG = "NativeX"
private val mImageService: ImageService = ImageService(mActivity) private val mImageService: ImageService = ImageService(mActivity)
@ -99,6 +100,31 @@ class NativeX(private val mActivity: AppCompatActivity) {
mDlService!!.queue(url, filename) 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) @Throws(Exception::class)
private fun routerGet(path: String): WebResourceResponse { private fun routerGet(path: String): WebResourceResponse {
val parts = path.split("/").toTypedArray() val parts = path.split("/").toTypedArray()

View File

@ -6,10 +6,18 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> tools:context=".MainActivity">
<androidx.media3.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="gone"
/>
<WebView <WebView
android:id="@+id/webview" android:id="@+id/webview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
/> />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>