From faae20939e369992168ce40e7ed4c51d810ab36f Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 18 Apr 2024 03:25:24 -0700 Subject: [PATCH] android player smoother basic caching prep cache somewhat works backup other files android impl blegh lets go touchup add prefetch to js use caching --- .../android/build.gradle | 95 +++++++++++++++++++ .../android/src/main/AndroidManifest.xml | 2 + .../ExpoBlueskyVideoPlayerModule.kt | 51 ++++++++++ .../ExpoBlueskyVideoPlayerView.kt | 69 ++++++++++++++ .../blueskyvideoplayer/MediaItemManager.kt | 72 ++++++++++++++ .../ios/ExpoBlueskyVideoPlayerModule.swift | 26 +++-- .../ios/ExpoBlueskyVideoPlayerView.swift | 30 ++++-- .../ios/PlayerController.swift | 84 ++++++++-------- .../ios/PlayerControllerManager.swift | 26 +++-- .../ios/PlayerItemManager.swift | 72 ++++++++------ .../src/VideoPlayer.tsx | 8 +- .../src/VideoPlayer.types.ts | 5 +- src/view/com/util/List.tsx | 2 + 13 files changed, 443 insertions(+), 99 deletions(-) create mode 100644 modules/expo-bluesky-video-player/android/build.gradle create mode 100644 modules/expo-bluesky-video-player/android/src/main/AndroidManifest.xml create mode 100644 modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerModule.kt create mode 100644 modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerView.kt create mode 100644 modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/MediaItemManager.kt diff --git a/modules/expo-bluesky-video-player/android/build.gradle b/modules/expo-bluesky-video-player/android/build.gradle new file mode 100644 index 0000000000..6272ee9f34 --- /dev/null +++ b/modules/expo-bluesky-video-player/android/build.gradle @@ -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' +} diff --git a/modules/expo-bluesky-video-player/android/src/main/AndroidManifest.xml b/modules/expo-bluesky-video-player/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..bdae66c8f5 --- /dev/null +++ b/modules/expo-bluesky-video-player/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerModule.kt b/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerModule.kt new file mode 100644 index 0000000000..9f1e9ec01a --- /dev/null +++ b/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerModule.kt @@ -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) + } + } + } + } +} diff --git a/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerView.kt b/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerView.kt new file mode 100644 index 0000000000..90f95a1447 --- /dev/null +++ b/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/ExpoBlueskyVideoPlayerView.kt @@ -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 + } + } +} diff --git a/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/MediaItemManager.kt b/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/MediaItemManager.kt new file mode 100644 index 0000000000..0feaca2fc2 --- /dev/null +++ b/modules/expo-bluesky-video-player/android/src/main/java/expo/modules/blueskyvideoplayer/MediaItemManager.kt @@ -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) + } +} \ No newline at end of file diff --git a/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerModule.swift b/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerModule.swift index 46a38b9eb6..36a0f278fb 100644 --- a/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerModule.swift +++ b/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerModule.swift @@ -4,12 +4,9 @@ public class ExpoBlueskyVideoPlayerModule: Module { public func definition() -> ModuleDefinition { Name("ExpoBlueskyVideoPlayer") - AsyncFunction("setShouldAutoplayAsync") { (value: Bool) in - - } - - AsyncFunction("prefetchAsync") { (source: String) in - PlayerItemManager.shared.getOrAddItem(source: source) + AsyncFunction("prefetchAsync") { (source: String, promise: Promise) in + PlayerItemManager.shared.saveToCache(source: source) + promise.resolve() } View(ExpoBlueskyVideoPlayerView.self) { @@ -19,16 +16,27 @@ public class ExpoBlueskyVideoPlayerModule: Module { view.source = prop } + Prop("autoplay") { (view: ExpoBlueskyVideoPlayerView, prop: Bool) in + view.autoplay = prop + } + AsyncFunction("getIsPlayingAsync") { (view: ExpoBlueskyVideoPlayerView, promise: Promise) in promise.resolve(view.isPlaying) } - AsyncFunction("playAsync") { (view: ExpoBlueskyVideoPlayerView) in - view.play() + AsyncFunction("toggleAsync") { (view: ExpoBlueskyVideoPlayerView, promise: Promise) in + view.toggle() + promise.resolve() } - AsyncFunction("pauseAsync") { (view: ExpoBlueskyVideoPlayerView) in + AsyncFunction("playAsync") { (view: ExpoBlueskyVideoPlayerView, promise: Promise) in + view.play() + promise.resolve() + } + + AsyncFunction("pauseAsync") { (view: ExpoBlueskyVideoPlayerView, promise: Promise) in view.pause() + promise.resolve() } } } diff --git a/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerView.swift b/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerView.swift index afdec24312..ec59b027f2 100644 --- a/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerView.swift +++ b/modules/expo-bluesky-video-player/ios/ExpoBlueskyVideoPlayerView.swift @@ -1,8 +1,15 @@ import ExpoModulesCore public class ExpoBlueskyVideoPlayerView: ExpoView, AVPlayerViewControllerDelegate { - public var source: String? = nil - public var isPlaying: Bool = true + public var source: String? = nil { + didSet { + if self.controller != nil, let source = source { + self.controller?.initForView(source, view: self) + } + } + } + public var isPlaying = true + public var autoplay = true private var controller: PlayerController? = nil @@ -25,12 +32,11 @@ public class ExpoBlueskyVideoPlayerView: ExpoView, AVPlayerViewControllerDelegat return } - if let controller = PlayerControllerManager.shared.getPlayer() { - controller.setFrame(rect: bounds) - controller.initForView(source, view: self) - self.addSubview(controller.view) - self.controller = controller - } + let controller = PlayerControllerManager.shared.getController() + controller.setFrame(rect: bounds) + controller.initForView(source, view: self) + self.addSubview(controller.view) + self.controller = controller } else { self.controller?.release() } @@ -45,4 +51,12 @@ public class ExpoBlueskyVideoPlayerView: ExpoView, AVPlayerViewControllerDelegat self.isPlaying = false self.controller?.pause() } + + func toggle() { + if self.isPlaying { + self.pause() + } else { + self.play() + } + } } diff --git a/modules/expo-bluesky-video-player/ios/PlayerController.swift b/modules/expo-bluesky-video-player/ios/PlayerController.swift index 6eb9765928..3b9cda88e5 100644 --- a/modules/expo-bluesky-video-player/ios/PlayerController.swift +++ b/modules/expo-bluesky-video-player/ios/PlayerController.swift @@ -1,9 +1,9 @@ import AVKit class PlayerController: AVPlayerViewController, AVPlayerViewControllerDelegate { - private var playerLooper: AVPlayerLooper? = nil var source: String? = nil var _superview: ExpoBlueskyVideoPlayerView? = nil + var needsCaching = true var isInUse = false var playerItem: AVPlayerItem? { @@ -11,13 +11,7 @@ class PlayerController: AVPlayerViewController, AVPlayerViewControllerDelegate { return self.player?.currentItem } set { - if newValue == nil { - if let player = self.player as? AVQueuePlayer { - player.removeAllItems() - } - } else { - self.player?.replaceCurrentItem(with: newValue) - } + self.player?.replaceCurrentItem(with: newValue) } } var itemStatus: AVPlayerItem.Status? { @@ -32,7 +26,11 @@ class PlayerController: AVPlayerViewController, AVPlayerViewControllerDelegate { override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nil, bundle: nil) - self.player = AVQueuePlayer() + + let player = AVPlayer() + player.actionAtItemEnd = .none + player.volume = .zero + self.player = player self.delegate = self self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] @@ -44,63 +42,69 @@ class PlayerController: AVPlayerViewController, AVPlayerViewControllerDelegate { if #available(iOS 16.0, *) { self.allowsVideoFrameAnalysis = false } - - self.player?.actionAtItemEnd = .pause - self.player?.volume = .zero + } + + deinit { + self.release() } func setFrame(rect: CGRect) { self.view.frame = rect } - public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - if keyPath == "status", let playerItem = object as? AVPlayerItem { - if playerItem.status == .readyToPlay, let player = self.player as? AVQueuePlayer { - // GIFs frequently have some black frames at the end of the video. To account for that, we offset the duration by 100ms, - // which should be enough frames to prevent a flicker. - player.play() - self.setLooper() + @objc func playerItemDidReachEnd(notification: Notification) { + if let playerItem = notification.object as? AVPlayerItem { + playerItem.seek(to: CMTime.zero, completionHandler: nil) + + if self.needsCaching, let asset = self.playerItem?.asset as? AVURLAsset { + PlayerItemManager.shared.saveToCache(source: asset.url.absoluteString) + self.needsCaching = false } } } - private func setLooper() { - guard let player = self.player as? AVQueuePlayer, let playerItem = self.playerItem else { - return + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "status", let playerItem = object as? AVPlayerItem { + if playerItem.status == .readyToPlay, let player = self.player { + if _superview?.autoplay == true, _superview?.isPlaying == true { + player.play() + } + } } - - let duration = playerItem.duration - self.playerLooper = AVPlayerLooper( - player: player, - templateItem: playerItem, - timeRange: CMTimeRange( - start: CMTime(value: 0, timescale: duration.timescale), - duration: CMTime(value: duration.value - 100, timescale: 1000) - ) - ) } func initForView(_ source: String, view: ExpoBlueskyVideoPlayerView) { - if let playerItem = PlayerItemManager.shared.getOrAddItem(source: source) { + if let playerItemAndInfo = PlayerItemManager.shared.getItem(source: source) { + let playerItem = playerItemAndInfo.0 + self.needsCaching = !playerItemAndInfo.1 + playerItem.addObserver(self, forKeyPath: "status", options: [.old, .new], context: nil) + NotificationCenter.default.addObserver( + self, + selector: #selector(playerItemDidReachEnd(notification:)), + name: AVPlayerItem.didPlayToEndTimeNotification, + object: playerItem + ) + self.isInUse = true self.source = source self.playerItem = playerItem self._superview = view - - if view.isPlaying, self.itemStatus == .readyToPlay { - self.play() - self.setLooper() - } } } func release() { + NotificationCenter.default.removeObserver( + self, + name: AVPlayerItem.didPlayToEndTimeNotification, + object: self.playerItem + ) + + self.needsCaching = true self.isInUse = false self.pause() - self.source = nil - self.playerLooper = nil self.playerItem = nil + self.removeFromParent() } func play() { diff --git a/modules/expo-bluesky-video-player/ios/PlayerControllerManager.swift b/modules/expo-bluesky-video-player/ios/PlayerControllerManager.swift index 86b26412a3..702e267d6a 100644 --- a/modules/expo-bluesky-video-player/ios/PlayerControllerManager.swift +++ b/modules/expo-bluesky-video-player/ios/PlayerControllerManager.swift @@ -4,29 +4,37 @@ class PlayerControllerManager { static let shared = PlayerControllerManager() private var controllers: [PlayerController] = [] - private let max = 10 - func getPlayer() -> PlayerController? { + func getController() -> PlayerController { if let controller = self.controllers.first(where: { $0.isInUse == false }) { return controller - } else if controllers.count < self.max { + } else { let controller = PlayerController() - controllers.append(controller) + self.controllers.append(controller) return controller } - return nil } - func releasePlayer(controller: PlayerController) { - if let controller = controllers.first(where: { $0 === controller }) { + func releaseController(controller: PlayerController) { + if let controller = self.controllers.first(where: { $0 === controller }) { controller.release() } } - func findBySource(source: String) -> PlayerController? { - if let controller = controllers.first(where: { $0.source == source}) { + func findByItem(_ item: AVPlayerItem) -> PlayerController? { + if let controller = self.controllers.first(where: { $0.playerItem === item}) { return controller } return nil } + + func cleanupExcessiveControllers() { + if self.controllers.count <= 15 { + return + } + + self.controllers.removeAll { c in + return !c.isInUse + } + } } diff --git a/modules/expo-bluesky-video-player/ios/PlayerItemManager.swift b/modules/expo-bluesky-video-player/ios/PlayerItemManager.swift index bfcd6249aa..828620c241 100644 --- a/modules/expo-bluesky-video-player/ios/PlayerItemManager.swift +++ b/modules/expo-bluesky-video-player/ios/PlayerItemManager.swift @@ -1,44 +1,58 @@ +import Foundation import AVKit +import CryptoKit class PlayerItemManager { static let shared = PlayerItemManager() - private var items: [(String, AVPlayerItem)] = [] - private let max = 30 + private var prefetchQueue: [String] = [] - private func addItem(source: String) -> AVPlayerItem? { - guard let url = URL(string: source) else { - return nil + func getItem(source: String) -> (AVPlayerItem, Bool)? { + let path = createPath(source) + + if FileManager.default.fileExists(atPath: path.path) { + return (AVPlayerItem(url: path), true) + } else if let url = URL(string: source) { + return (AVPlayerItem(url: url), false) } - - if items.count >= self.max { - let first = self.items.removeFirst() - self.removeItem(source: first.0) - } - - let item = AVPlayerItem(url: url) - self.items.append((source, item)) - - return item + return nil } - func getOrAddItem(source: String) -> AVPlayerItem? { - if let index = items.firstIndex(where: { $0.0 == source }) { - let item = self.items[index] - self.items.move(fromOffsets: IndexSet(integer: index), toOffset: self.items.count - 1) - return item.1 - } - - return self.addItem(source: source) - } - - func removeItem(source: String?) { - guard let source = source else { + func saveToCache(source: String) { + guard let sourceUrl = URL(string: source) else { return } - if let controller = PlayerControllerManager.shared.findBySource(source: source) { - controller.release() + let path = createPath(sourceUrl.absoluteString) + let downloadTask = URLSession.shared.downloadTask(with:sourceUrl) { tempLocalURL, response, error in + if let tempLocalURL = tempLocalURL, error == nil { + try? FileManager.default.moveItem(at: tempLocalURL, to: path) + } } + downloadTask.resume() + } + + private func getHash(_ source: String) -> String { + let data = Data(source.utf8) + let hash = Insecure.SHA1.hash(data: data) + return hash.map { String(format: "%02hhx", $0)}.joined() + } + + private func getGifsDirectory() -> URL { + let paths = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask) + var cacheDir = paths[0] + cacheDir = cacheDir.appendingPathComponent("gifs", isDirectory: true) + + if !FileManager.default.fileExists(atPath: cacheDir.path) { + try? FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: false) + } + + return cacheDir + } + + private func createPath(_ source: String) -> URL { + let hash = getHash(source) + var gifsDir = getGifsDirectory() + return gifsDir.appendingPathComponent("\(hash).mp4", isDirectory: false) } } diff --git a/modules/expo-bluesky-video-player/src/VideoPlayer.tsx b/modules/expo-bluesky-video-player/src/VideoPlayer.tsx index 2a1b3d458c..e5ca515136 100644 --- a/modules/expo-bluesky-video-player/src/VideoPlayer.tsx +++ b/modules/expo-bluesky-video-player/src/VideoPlayer.tsx @@ -14,8 +14,8 @@ export class VideoPlayer extends React.PureComponent { this.nativeRef = createRef() } - static async setShouldAutoplayAsync(shouldAutoplay: boolean): Promise { - await VideoModule.setShouldAutoplayAsync(shouldAutoplay) + static async prefetchAsync(source: string): Promise { + await VideoModule.prefetchAsync(source) } async playAsync(): Promise { @@ -26,6 +26,10 @@ export class VideoPlayer extends React.PureComponent { await this.nativeRef.current.pauseAsync() } + async toggleAsync(): Promise { + await this.nativeRef.current.toggleAsync() + } + render() { return } diff --git a/modules/expo-bluesky-video-player/src/VideoPlayer.types.ts b/modules/expo-bluesky-video-player/src/VideoPlayer.types.ts index 9d2f277a2e..3a8e9444b0 100644 --- a/modules/expo-bluesky-video-player/src/VideoPlayer.types.ts +++ b/modules/expo-bluesky-video-player/src/VideoPlayer.types.ts @@ -7,6 +7,7 @@ export interface VideoPlayerLoadEvent { } export interface VideoPlayerViewProps extends ViewProps { - source: string | null - onLoad: (event: VideoPlayerLoadEvent) => void + autoplay?: boolean + source?: string + onLoad?: (event: VideoPlayerLoadEvent) => void } diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx index b3bde2a118..401b5935ad 100644 --- a/src/view/com/util/List.tsx +++ b/src/view/com/util/List.tsx @@ -87,6 +87,8 @@ function ListImpl( contentOffset = {x: 0, y: headerOffset * -1} } + console.log(props.removeClippedSubviews) + return (