Add local video playback
parent
ce3ee760d0
commit
403e4404a7
|
@ -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"
|
||||||
}
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue