android player

smoother

basic caching

prep cache

somewhat works

backup

other files

android impl

blegh

lets go

touchup

add prefetch to js

use caching
This commit is contained in:
Hailey
2024-04-18 03:25:24 -07:00
parent 4cb5f8a005
commit faae20939e
13 changed files with 443 additions and 99 deletions
@@ -0,0 +1,95 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'
group = 'expo.modules.blueskyvideoplayer'
version = '0.5.0'
buildscript {
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
if (expoModulesCorePlugin.exists()) {
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
}
// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
// Ensures backward compatibility
ext.getKotlinVersion = {
if (ext.has("kotlinVersion")) {
ext.kotlinVersion()
} else {
ext.safeExtGet("kotlinVersion", "1.8.10")
}
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
}
}
afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
}
}
repositories {
maven {
url = mavenLocal().url
}
}
}
}
android {
compileSdkVersion safeExtGet("compileSdkVersion", 33)
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.majorVersion
}
}
namespace "expo.modules.blueskyvideoplayer"
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
targetSdkVersion safeExtGet("targetSdkVersion", 34)
versionCode 1
versionName "0.5.0"
}
lintOptions {
abortOnError false
}
publishing {
singleVariant("release") {
withSourcesJar()
}
}
}
repositories {
mavenCentral()
}
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
implementation 'androidx.media3:media3-exoplayer:1.3.1'
implementation 'androidx.media3:media3-ui:1.3.1'
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
}
@@ -0,0 +1,2 @@
<manifest>
</manifest>
@@ -0,0 +1,51 @@
package expo.modules.blueskyvideoplayer
import expo.modules.kotlin.Promise
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoBlueskyVideoPlayerModule : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoBlueskyVideoPlayer")
AsyncFunction("prefetchAsync") {
{ source: String, promise: Promise ->
MediaItemManager(appContext).saveToCache(source)
promise.resolve()
}
}
View(ExpoBlueskyVideoPlayerView::class) {
Prop("source") { view: ExpoBlueskyVideoPlayerView, source: String ->
view.source = source
}
Prop("autoplay") { view: ExpoBlueskyVideoPlayerView, autoplay: Boolean ->
view.autoplay = autoplay
}
Prop("getIsPlayingAsync") { view, promise: Promise ->
promise.resolve(view.isPlaying)
}
AsyncFunction("playAsync") { view: ExpoBlueskyVideoPlayerView ->
view.play()
}
AsyncFunction("pauseAsync") { view: ExpoBlueskyVideoPlayerView ->
view.pause()
}
AsyncFunction("toggleAsync") { view: ExpoBlueskyVideoPlayerView ->
view.toggle()
}
OnViewDidUpdateProps {
val source = it.source
if (source != null) {
it.updateSource(source)
}
}
}
}
}
@@ -0,0 +1,69 @@
package expo.modules.blueskyvideoplayer
import android.content.Context
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.views.ExpoView
class ExpoBlueskyVideoPlayerView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
private var playerView: PlayerView
private var player: ExoPlayer
var autoplay: Boolean = true
var source: String? = null
var isPlaying = true
init {
this.playerView = PlayerView(context).apply {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
useController = false
}
this.player = ExoPlayer.Builder(context).build().also {
playerView.player = it
}.apply {
repeatMode = ExoPlayer.REPEAT_MODE_ONE
volume = 0f
}
this.addView(this.playerView)
}
override fun onAttachedToWindow() {
if (this.autoplay && this.isPlaying) {
this.player.play()
}
super.onAttachedToWindow()
}
override fun onDetachedFromWindow() {
this.player.pause()
super.onDetachedFromWindow()
}
fun updateSource(source: String) {
val mediaItem = MediaItemManager(appContext).getItem(source)
player.setMediaItem(mediaItem)
player.prepare()
player.playWhenReady = true
}
fun play() {
this.player.play()
this.isPlaying = true
}
fun pause() {
this.player.pause()
this.isPlaying = false
}
fun toggle() {
if (this.isPlaying) {
this.player.pause()
this.isPlaying = false
} else {
this.player.play()
this.isPlaying = true
}
}
}
@@ -0,0 +1,72 @@
package expo.modules.blueskyvideoplayer
import android.net.Uri
import androidx.media3.common.MediaItem
import expo.modules.kotlin.AppContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okio.IOException
import java.io.File
import java.io.FileOutputStream
import java.security.MessageDigest
class MediaItemManager(private val appContext: AppContext) {
companion object {
private val client = OkHttpClient()
}
fun getItem(source: String): MediaItem {
val path = createPath(source)
return if (File(path).exists()) {
MediaItem.fromUri(Uri.parse(path))
} else {
saveToCache(source)
MediaItem.fromUri(Uri.parse(source))
}
}
fun saveToCache(source: String) {
val request = Request.Builder()
.url(source)
.build()
client.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
e.printStackTrace() // Handle the error
}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
val path = createPath(source)
if (response.isSuccessful) {
val file = File(path)
val fos = FileOutputStream(file)
val inputStream = response.body?.byteStream()
inputStream?.use { input ->
fos.use { fileOut ->
input.copyTo(fileOut)
}
}
}
}
})
}
private fun getHash(source: String): String {
val md = MessageDigest.getInstance("SHA-1")
val byteArray = md.digest(source.toByteArray())
return byteArray.joinToString("") { "%02x".format(it) }
}
private fun getGifsDirectory(): String {
val gifsDirectory = appContext.cacheDirectory.path + "/gifs"
if (!File(gifsDirectory).exists()) {
File(gifsDirectory).mkdir()
}
return gifsDirectory
}
private fun createPath(source: String): String {
return getGifsDirectory() + "/" + getHash(source)
}
}