this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'main' into aliceisjustplaying-patch-1

authored by

Alice and committed by
GitHub
ad0144d2 89767014

+599 -527
+54 -49
android/src/main/java/expo/modules/blueskyvideo/BlueskyVideoModule.kt
··· 10 10 11 11 @UnstableApi 12 12 class BlueskyVideoModule : Module() { 13 - private var wasPlayingPlayer: Player? = null 13 + private var wasPlayingPlayer: Player? = null 14 14 15 - override fun definition() = ModuleDefinition { 16 - Name("BlueskyVideo") 15 + override fun definition() = 16 + ModuleDefinition { 17 + Name("BlueskyVideo") 17 18 18 - OnActivityEntersForeground { 19 - val view = ViewManager.getActiveView() ?: return@OnActivityEntersForeground 20 - val player = view.player ?: return@OnActivityEntersForeground 19 + OnActivityEntersForeground { 20 + val view = ViewManager.getActiveView() ?: return@OnActivityEntersForeground 21 + val player = view.player ?: return@OnActivityEntersForeground 21 22 22 - if (player.isPlaying) { 23 - wasPlayingPlayer = player 24 - player.pause() 25 - } 26 - } 23 + if (player.isPlaying) { 24 + wasPlayingPlayer = player 25 + player.pause() 26 + } 27 + } 27 28 28 - OnActivityEntersBackground { 29 - val player = wasPlayingPlayer ?: return@OnActivityEntersBackground 30 - player.play() 31 - wasPlayingPlayer = null 32 - } 29 + OnActivityEntersBackground { 30 + val player = wasPlayingPlayer ?: return@OnActivityEntersBackground 31 + player.play() 32 + wasPlayingPlayer = null 33 + } 33 34 34 - AsyncFunction("updateActiveVideoViewAsync") { 35 - val handler = Handler(Looper.getMainLooper()) 36 - handler.post { 37 - ViewManager.updateActiveView() 38 - } 39 - } 35 + AsyncFunction("updateActiveVideoViewAsync") { 36 + val handler = Handler(Looper.getMainLooper()) 37 + handler.post { 38 + ViewManager.updateActiveView() 39 + } 40 + } 41 + 42 + View(BlueskyVideoView::class) { 43 + Events( 44 + "onActiveChange", 45 + "onLoadingChange", 46 + "onMutedChange", 47 + "onPlayerPress", 48 + "onStatusChange", 49 + "onTimeRemainingChange", 50 + "onError", 51 + ) 40 52 41 - View(BlueskyVideoView::class) { 42 - Events( 43 - "onStatusChange", 44 - "onMutedChange", 45 - "onTimeRemainingChange", 46 - "onLoadingChange", 47 - "onActiveChange", 48 - "onPlayerPress", 49 - "onError", 50 - ) 53 + Prop("url") { view: BlueskyVideoView, prop: Uri -> 54 + view.url = prop 55 + } 51 56 52 - Prop("url") { view: BlueskyVideoView, prop: Uri -> 53 - view.url = prop 54 - } 57 + Prop("autoplay") { view: BlueskyVideoView, prop: Boolean -> 58 + view.autoplay = prop 59 + } 55 60 56 - Prop("autoplay") { view: BlueskyVideoView, prop: Boolean -> 57 - view.autoplay = prop 58 - } 61 + Prop("beginMuted") { view: BlueskyVideoView, prop: Boolean -> 62 + view.beginMuted = prop 63 + } 59 64 60 - AsyncFunction("togglePlayback") { view: BlueskyVideoView -> 61 - view.togglePlayback() 62 - } 65 + AsyncFunction("togglePlayback") { view: BlueskyVideoView -> 66 + view.togglePlayback() 67 + } 63 68 64 - AsyncFunction("toggleMuted") { view: BlueskyVideoView -> 65 - view.toggleMuted() 66 - } 69 + AsyncFunction("toggleMuted") { view: BlueskyVideoView -> 70 + view.toggleMuted() 71 + } 67 72 68 - AsyncFunction("enterFullscreen") { view: BlueskyVideoView -> 69 - view.enterFullscreen() 70 - } 71 - } 72 - } 73 + AsyncFunction("enterFullscreen") { view: BlueskyVideoView -> 74 + view.enterFullscreen() 75 + } 76 + } 77 + } 73 78 }
+266 -223
android/src/main/java/expo/modules/blueskyvideo/BlueskyVideoView.kt
··· 16 16 import expo.modules.kotlin.viewevent.EventDispatcher 17 17 import expo.modules.kotlin.views.ExpoView 18 18 import expo.modules.video.ProgressTracker 19 - import kotlinx.coroutines.Dispatchers 20 - import kotlinx.coroutines.withContext 21 19 import kotlinx.coroutines.CoroutineScope 20 + import kotlinx.coroutines.Dispatchers 22 21 import kotlinx.coroutines.Job 23 22 import kotlinx.coroutines.launch 23 + import kotlinx.coroutines.withContext 24 24 import java.lang.ref.WeakReference 25 25 26 26 @UnstableApi 27 - class BlueskyVideoView(context: Context, appContext: AppContext) : ExpoView(context, appContext) { 28 - private val playerScope = CoroutineScope(Job() + Dispatchers.Main) 29 - private val playerView: PlayerView 30 - var player: ExoPlayer? = null 31 - private var progressTracker: ProgressTracker? = null 27 + class BlueskyVideoView( 28 + context: Context, 29 + appContext: AppContext, 30 + ) : ExpoView(context, appContext) { 31 + private val playerScope = CoroutineScope(Job() + Dispatchers.Main) 32 32 33 - var url: Uri? = null 33 + private val playerView: PlayerView 34 + var player: ExoPlayer? = null 34 35 35 - var autoplay = false 36 + private var progressTracker: ProgressTracker? = null 36 37 37 - private var isFullscreen: Boolean = false 38 - set(value) { 39 - field = value 40 - if (value) { 41 - this.playerView.useController = true 42 - this.playerView.player?.play() 43 - } else { 44 - this.playerView.useController = false 45 - } 46 - } 38 + var url: Uri? = null 39 + var autoplay = false 40 + var beginMuted = true 41 + var ignoreAutoplay = false 47 42 48 - private var isPlaying: Boolean = false 49 - set(value) { 50 - field = value 51 - onStatusChange(mapOf( 52 - "status" to if (value) "playing" else "paused" 53 - )) 54 - } 43 + private var isFullscreen: Boolean = false 44 + set(value) { 45 + field = value 46 + if (value) { 47 + this.playerView.useController = true 48 + this.playerView.player?.play() 49 + } else { 50 + this.playerView.useController = false 51 + } 52 + } 53 + 54 + private var isPlaying: Boolean = false 55 + set(value) { 56 + field = value 57 + onStatusChange( 58 + mapOf( 59 + "status" to if (value) "playing" else "paused", 60 + ), 61 + ) 62 + } 63 + 64 + private var isMuted: Boolean = false 65 + set(value) { 66 + field = value 67 + onMutedChange( 68 + mapOf( 69 + "isMuted" to value, 70 + ), 71 + ) 72 + } 55 73 56 - private var isMuted: Boolean = false 57 - set(value) { 58 - field = value 59 - onMutedChange(mapOf( 60 - "isMuted" to value 61 - )) 62 - } 74 + private var isLoading: Boolean = false 75 + set(value) { 76 + field = value 77 + onLoadingChange( 78 + mapOf( 79 + "isLoading" to value, 80 + ), 81 + ) 82 + } 63 83 64 - private var isLoading: Boolean = false 65 - set(value) { 66 - field = value 67 - onLoadingChange(mapOf( 68 - "isLoading" to value 69 - )) 70 - } 84 + private var isViewActive: Boolean = false 85 + set(value) { 86 + field = value 87 + onActiveChange( 88 + mapOf( 89 + "isActive" to value, 90 + ), 91 + ) 92 + } 71 93 72 - private var isViewActive: Boolean = false 73 - set(value) { 74 - field = value 75 - onActiveChange(mapOf( 76 - "isActive" to value 77 - )) 78 - } 94 + private val onActiveChange by EventDispatcher() 95 + private val onLoadingChange by EventDispatcher() 96 + private val onMutedChange by EventDispatcher() 97 + private val onPlayerPress by EventDispatcher() 98 + private val onStatusChange by EventDispatcher() 99 + private val onTimeRemainingChange by EventDispatcher() 100 + private val onError by EventDispatcher() 79 101 80 - private val onStatusChange by EventDispatcher() 81 - private val onLoadingChange by EventDispatcher() 82 - private val onActiveChange by EventDispatcher() 83 - private val onTimeRemainingChange by EventDispatcher() 84 - private val onMutedChange by EventDispatcher() 85 - private val onError by EventDispatcher() 86 - private val onPlayerPress by EventDispatcher() 102 + private var enteredFullscreenMuteState = true 87 103 88 - init { 89 - val playerView = PlayerView(context).apply { 90 - setBackgroundColor(Color.BLACK) 91 - resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM 92 - useController = false 93 - setOnClickListener { _ -> 94 - onPlayerPress(mapOf()) 95 - } 104 + init { 105 + val playerView = 106 + PlayerView(context).apply { 107 + setBackgroundColor(Color.BLACK) 108 + resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM 109 + useController = false 110 + setOnClickListener { _ -> 111 + onPlayerPress(mapOf()) 112 + } 113 + } 114 + this.addView( 115 + playerView, 116 + ViewGroup.LayoutParams( 117 + ViewGroup.LayoutParams.MATCH_PARENT, 118 + ViewGroup.LayoutParams.MATCH_PARENT, 119 + ), 120 + ) 121 + this.playerView = playerView 96 122 } 97 - this.addView( 98 - playerView, 99 - ViewGroup.LayoutParams( 100 - ViewGroup.LayoutParams.MATCH_PARENT, 101 - ViewGroup.LayoutParams.MATCH_PARENT 102 - ) 103 - ) 104 - this.playerView = playerView 105 - } 123 + 124 + // Lifecycle 106 125 107 - // Lifecycle 126 + private fun setup() { 127 + // We shouldn't encounter this scenario, but would rather be safe than sorry here and just 128 + // skip setup if we do. 129 + if (this.player != null) { 130 + return 131 + } 108 132 109 - private fun playVideo() { 110 - if (this.player != null) { 111 - return 112 - } 133 + this.isLoading = true 113 134 114 - val player = this.createExoPlayer() 115 - this.player = player 116 - this.playerView.player = player 135 + val player = this.createExoPlayer() 136 + this.player = player 137 + this.playerView.player = player 117 138 118 - playerScope.launch { 119 - val mediaItem = createMediaItem() 120 - player.setMediaItem(mediaItem) 121 - player.prepare() 139 + playerScope.launch { 140 + val mediaItem = createMediaItem() 141 + player.setMediaItem(mediaItem) 142 + player.prepare() 143 + } 122 144 } 123 - } 124 145 125 - private fun removeVideo() { 126 - val player = this.player ?: return 146 + private fun destroy() { 147 + val player = this.player ?: return 127 148 128 - this.mute() 129 - this.pause() 130 - this.isLoading = true 149 + this.isLoading = false 150 + this.ignoreAutoplay = false 131 151 132 - player.release() 133 - this.player = null 134 - this.playerView.player = null 135 - this.isLoading = false 136 - } 152 + this.mute() 153 + this.pause() 154 + this.isLoading = true 137 155 138 - override fun onAttachedToWindow() { 139 - super.onAttachedToWindow() 140 - ViewManager.addView(this) 141 - } 156 + player.release() 157 + this.player = null 158 + this.playerView.player = null 159 + } 142 160 143 - override fun onDetachedFromWindow() { 144 - super.onDetachedFromWindow() 145 - ViewManager.removeView(this) 146 - } 161 + override fun onAttachedToWindow() { 162 + super.onAttachedToWindow() 163 + ViewManager.addView(this) 164 + } 147 165 148 - // Controls 166 + override fun onDetachedFromWindow() { 167 + super.onDetachedFromWindow() 168 + ViewManager.removeView(this) 169 + } 149 170 150 - private fun play() { 151 - this.addProgressTracker() 152 - this.player?.play() 153 - this.isPlaying = true 154 - } 171 + // Controls 155 172 156 - private fun pause() { 157 - this.removeProgressTracker() 158 - this.player?.pause() 159 - this.isPlaying = false 160 - } 173 + private fun play() { 174 + this.addProgressTracker() 175 + this.player?.play() 176 + this.isPlaying = true 177 + } 161 178 162 - fun togglePlayback() { 163 - if (this.isPlaying) { 164 - pause() 165 - } else { 166 - play() 179 + private fun pause() { 180 + this.removeProgressTracker() 181 + this.player?.pause() 182 + this.isPlaying = false 167 183 } 168 - } 169 184 170 - private fun mute() { 171 - this.player?.volume = 0f 172 - this.isMuted = true 173 - } 185 + fun togglePlayback() { 186 + if (this.isPlaying) { 187 + this.pause() 188 + } else { 189 + if (this.player == null) { 190 + this.ignoreAutoplay = true 191 + this.setup() 192 + } else { 193 + this.play() 194 + } 195 + } 196 + } 174 197 175 - private fun unmute() { 176 - this.player?.volume = 1f 177 - this.isMuted = false 178 - } 198 + private fun mute() { 199 + this.player?.volume = 0f 200 + this.isMuted = true 201 + } 179 202 180 - fun toggleMuted() { 181 - if (this.isMuted) { 182 - unmute() 183 - } else { 184 - mute() 203 + private fun unmute() { 204 + this.player?.volume = 1f 205 + this.isMuted = false 185 206 } 186 - } 187 207 188 - fun enterFullscreen() { 189 - val currentActivity = this.appContext.currentActivity ?: return 190 - 191 - this.unmute() 192 - if (!this.isPlaying) { 193 - this.play() 208 + fun toggleMuted() { 209 + if (this.isMuted) { 210 + this.unmute() 211 + } else { 212 + this.mute() 213 + } 194 214 } 195 215 196 - // Remove the player from this view, but don't null the player! 197 - this.playerView.player = null 216 + fun enterFullscreen() { 217 + val currentActivity = this.appContext.currentActivity ?: return 198 218 199 - // create the intent and give it a view 200 - val intent = Intent(context, FullscreenActivity::class.java) 201 - FullscreenActivity.asscVideoView = WeakReference(this) 219 + this.enteredFullscreenMuteState = this.isMuted 202 220 203 - // fire the fullscreen event and launch the intent 204 - this.isFullscreen = true 205 - currentActivity.startActivity(intent) 206 - } 221 + // We always want to start with unmuted state and playing. Fire those from here so the 222 + // event dispatcher gets called 223 + this.unmute() 224 + if (!this.isPlaying) { 225 + this.play() 226 + } 207 227 208 - fun onExitFullscreen() { 209 - this.isFullscreen = false 210 - this.mute() 211 - if(autoplay) { 212 - this.play() 213 - } else { 214 - this.pause() 215 - } 216 - this.playerView.player = this.player 217 - } 228 + // Remove the player from this view, but don't null the player! 229 + this.playerView.player = null 218 230 219 - // Visibility 231 + // create the intent and give it a view 232 + val intent = Intent(context, FullscreenActivity::class.java) 233 + FullscreenActivity.asscVideoView = WeakReference(this) 220 234 221 - fun setIsCurrentlyActive(isActive: Boolean): Boolean { 222 - if (this.isFullscreen) { 223 - return false 235 + // fire the fullscreen event and launch the intent 236 + this.isFullscreen = true 237 + currentActivity.startActivity(intent) 224 238 } 225 239 226 - this.isViewActive = isActive 227 - if (isActive) { 228 - this.playVideo() 229 - } else { 230 - this.removeVideo() 240 + fun onExitFullscreen() { 241 + this.isFullscreen = false 242 + if (this.enteredFullscreenMuteState) { 243 + this.mute() 244 + } 245 + if (this.autoplay) { 246 + this.play() 247 + } else { 248 + this.pause() 249 + } 250 + this.playerView.player = this.player 231 251 } 232 - return true 233 - } 252 + 253 + // Visibility 254 + 255 + fun setIsCurrentlyActive(isActive: Boolean): Boolean { 256 + if (this.isFullscreen) { 257 + return false 258 + } 234 259 235 - fun getPositionOnScreen(): Rect? { 236 - if (!this.isShown) { 237 - return null 260 + this.isViewActive = isActive 261 + if (isActive) { 262 + if (this.autoplay) { 263 + this.setup() 264 + } 265 + } else { 266 + this.destroy() 267 + } 268 + return true 238 269 } 239 270 240 - val screenPosition = intArrayOf(0, 0) 241 - this.getLocationInWindow(screenPosition) 242 - return Rect( 243 - screenPosition[0], 244 - screenPosition[1], 245 - screenPosition[0] + this.width, 246 - screenPosition[1] + this.height, 247 - ) 248 - } 271 + fun getPositionOnScreen(): Rect? { 272 + if (!this.isShown) { 273 + return null 274 + } 249 275 250 - fun isViewableEnough(): Boolean { 251 - val positionOnScreen = this.getPositionOnScreen() ?: return false 252 - val visibleArea = positionOnScreen.width() * positionOnScreen.height() 253 - val totalArea = this.width * this.height 254 - return visibleArea >= 0.5 * totalArea 255 - } 276 + val screenPosition = intArrayOf(0, 0) 277 + this.getLocationInWindow(screenPosition) 278 + return Rect( 279 + screenPosition[0], 280 + screenPosition[1], 281 + screenPosition[0] + this.width, 282 + screenPosition[1] + this.height, 283 + ) 284 + } 256 285 257 - private suspend fun createMediaItem(): MediaItem { 258 - return withContext(Dispatchers.IO) { 259 - MediaItem.Builder() 260 - .setUri(url.toString()) 261 - .build() 286 + fun isViewableEnough(): Boolean { 287 + val positionOnScreen = this.getPositionOnScreen() ?: return false 288 + val visibleArea = positionOnScreen.width() * positionOnScreen.height() 289 + val totalArea = this.width * this.height 290 + return visibleArea >= 0.5 * totalArea 262 291 } 263 - } 264 292 265 - private fun createExoPlayer(): ExoPlayer { 266 - return ExoPlayer.Builder(context) 267 - .apply { 268 - setLooper(context.mainLooper) 269 - setSeekForwardIncrementMs(5000) 270 - setSeekBackIncrementMs(5000) 271 - } 272 - .build().apply { 273 - repeatMode = ExoPlayer.REPEAT_MODE_ALL 274 - volume = 0f 275 - addListener(object : Player.Listener { 276 - override fun onPlaybackStateChanged(playbackState: Int) { 277 - when (playbackState) { 278 - ExoPlayer.STATE_READY -> { 279 - if (this@BlueskyVideoView.autoplay) { 280 - this@BlueskyVideoView.isLoading = false 281 - this@BlueskyVideoView.play() 293 + // Setup helpers 294 + 295 + private suspend fun createMediaItem(): MediaItem = 296 + withContext(Dispatchers.IO) { 297 + MediaItem 298 + .Builder() 299 + .setUri(url.toString()) 300 + .build() 301 + } 302 + 303 + private fun createExoPlayer(): ExoPlayer = 304 + ExoPlayer 305 + .Builder(context) 306 + .apply { 307 + setLooper(context.mainLooper) 308 + setSeekForwardIncrementMs(5000) 309 + setSeekBackIncrementMs(5000) 310 + }.build() 311 + .apply { 312 + repeatMode = ExoPlayer.REPEAT_MODE_ALL 313 + if (beginMuted) { 314 + volume = 0f 282 315 } 283 - } 316 + addListener( 317 + object : Player.Listener { 318 + override fun onPlaybackStateChanged(playbackState: Int) { 319 + when (playbackState) { 320 + ExoPlayer.STATE_READY -> { 321 + val view = this@BlueskyVideoView 322 + if (view.autoplay || view.ignoreAutoplay) { 323 + view.isLoading = false 324 + view.play() 325 + } 326 + } 327 + } 328 + } 329 + }, 330 + ) 284 331 } 285 - } 286 - }) 287 - } 288 - } 289 332 290 - private fun addProgressTracker() { 291 - val player = this.playerView.player ?: return 292 - this.progressTracker = ProgressTracker(player, onTimeRemainingChange) 293 - } 333 + private fun addProgressTracker() { 334 + val player = this.playerView.player ?: return 335 + this.progressTracker = ProgressTracker(player, onTimeRemainingChange) 336 + } 294 337 295 - private fun removeProgressTracker() { 296 - this.progressTracker?.remove() 297 - this.progressTracker = null 298 - } 338 + private fun removeProgressTracker() { 339 + this.progressTracker?.remove() 340 + this.progressTracker = null 341 + } 299 342 }
+37 -35
android/src/main/java/expo/modules/blueskyvideo/FullscreenActivity.kt
··· 10 10 import java.lang.ref.WeakReference 11 11 12 12 @UnstableApi 13 - class FullscreenActivity: AppCompatActivity() { 14 - companion object { 15 - var asscVideoView: WeakReference<BlueskyVideoView>? = null 16 - } 13 + class FullscreenActivity : AppCompatActivity() { 14 + companion object { 15 + var asscVideoView: WeakReference<BlueskyVideoView>? = null 16 + } 17 17 18 - override fun onCreate(savedInstanceState: Bundle?) { 19 - val player = asscVideoView?.get()?.player ?: return 18 + override fun onCreate(savedInstanceState: Bundle?) { 19 + val player = asscVideoView?.get()?.player ?: return 20 20 21 - super.onCreate(savedInstanceState) 21 + super.onCreate(savedInstanceState) 22 22 23 - this.window.setFlags( 24 - WindowManager.LayoutParams.FLAG_FULLSCREEN, 25 - WindowManager.LayoutParams.FLAG_FULLSCREEN 26 - ) 23 + this.window.setFlags( 24 + WindowManager.LayoutParams.FLAG_FULLSCREEN, 25 + WindowManager.LayoutParams.FLAG_FULLSCREEN, 26 + ) 27 27 28 - // Update the player viewz 29 - val playerView = PlayerView(this).apply { 30 - setBackgroundColor(Color.BLACK) 31 - setShowSubtitleButton(true) 32 - setShowNextButton(false) 33 - setShowPreviousButton(false) 34 - setFullscreenButtonClickListener { 35 - finish() 36 - } 28 + // Update the player viewz 29 + val playerView = 30 + PlayerView(this).apply { 31 + setBackgroundColor(Color.BLACK) 32 + setShowSubtitleButton(true) 33 + setShowNextButton(false) 34 + setShowPreviousButton(false) 35 + setFullscreenButtonClickListener { 36 + finish() 37 + } 37 38 38 - layoutParams = ViewGroup.LayoutParams( 39 - ViewGroup.LayoutParams.MATCH_PARENT, 40 - ViewGroup.LayoutParams.MATCH_PARENT 41 - ) 42 - useController = true 43 - controllerAutoShow = false 44 - controllerHideOnTouch = true 45 - } 46 - playerView.player = player 39 + layoutParams = 40 + ViewGroup.LayoutParams( 41 + ViewGroup.LayoutParams.MATCH_PARENT, 42 + ViewGroup.LayoutParams.MATCH_PARENT, 43 + ) 44 + useController = true 45 + controllerAutoShow = false 46 + controllerHideOnTouch = true 47 + } 48 + playerView.player = player 47 49 48 - setContentView(playerView) 49 - } 50 + setContentView(playerView) 51 + } 50 52 51 - override fun onDestroy() { 52 - asscVideoView?.get()?.onExitFullscreen() 53 - super.onDestroy() 54 - } 53 + override fun onDestroy() { 54 + asscVideoView?.get()?.onExitFullscreen() 55 + super.onDestroy() 56 + } 55 57 }
+22 -20
android/src/main/java/expo/modules/blueskyvideo/ProgressTracker.kt
··· 4 4 import android.os.Looper 5 5 import androidx.media3.common.Player 6 6 import androidx.media3.common.util.UnstableApi 7 - import kotlin.math.floor 8 7 import expo.modules.kotlin.viewevent.ViewEventCallback 8 + import kotlin.math.floor 9 9 10 10 @androidx.annotation.OptIn(UnstableApi::class) 11 11 class ProgressTracker( 12 - private val player: Player, 13 - private val onTimeRemainingChange: ViewEventCallback<Map<String, Any>> 12 + private val player: Player, 13 + private val onTimeRemainingChange: ViewEventCallback<Map<String, Any>>, 14 14 ) : Runnable { 15 - private val handler: Handler = Handler(Looper.getMainLooper()) 15 + private val handler: Handler = Handler(Looper.getMainLooper()) 16 16 17 - init { 18 - handler.post(this) 19 - } 17 + init { 18 + handler.post(this) 19 + } 20 20 21 - override fun run() { 22 - val currentPosition = player.currentPosition 23 - val duration = player.duration 24 - val timeRemaining = floor(((duration - currentPosition) / 1000).toDouble()) 25 - onTimeRemainingChange(mapOf( 26 - "timeRemaining" to timeRemaining 27 - )) 28 - handler.postDelayed(this, 1000) 29 - } 21 + override fun run() { 22 + val currentPosition = player.currentPosition 23 + val duration = player.duration 24 + val timeRemaining = floor(((duration - currentPosition) / 1000).toDouble()) 25 + onTimeRemainingChange( 26 + mapOf( 27 + "timeRemaining" to timeRemaining, 28 + ), 29 + ) 30 + handler.postDelayed(this, 1000) 31 + } 30 32 31 - fun remove() { 32 - handler.removeCallbacks(this) 33 - } 34 - } 33 + fun remove() { 34 + handler.removeCallbacks(this) 35 + } 36 + }
+64 -66
android/src/main/java/expo/modules/blueskyvideo/ViewManager.kt
··· 5 5 6 6 @UnstableApi 7 7 class ViewManager { 8 - companion object { 9 - private val views = mutableSetOf<BlueskyVideoView>() 10 - private var currentlyActiveView: BlueskyVideoView? = null 11 - private var prevCount = 0 8 + companion object { 9 + private val views = mutableSetOf<BlueskyVideoView>() 10 + private var currentlyActiveView: BlueskyVideoView? = null 11 + private var prevCount = 0 12 12 13 - fun addView(view: BlueskyVideoView) { 14 - views.add(view) 15 - if (prevCount == 0) { 16 - this.updateActiveView() 17 - } 18 - prevCount = views.count() 19 - } 13 + fun addView(view: BlueskyVideoView) { 14 + views.add(view) 15 + if (prevCount == 0) { 16 + this.updateActiveView() 17 + } 18 + prevCount = views.count() 19 + } 20 20 21 - fun removeView(view: BlueskyVideoView) { 22 - views.remove(view) 23 - prevCount = views.count() 24 - } 21 + fun removeView(view: BlueskyVideoView) { 22 + views.remove(view) 23 + prevCount = views.count() 24 + } 25 25 26 - fun updateActiveView() { 27 - var activeView: BlueskyVideoView? = null 28 - val count = views.count() 26 + fun updateActiveView() { 27 + var activeView: BlueskyVideoView? = null 28 + val count = views.count() 29 29 30 - if (count == 1) { 31 - val view = views.first() 32 - if (view.isViewableEnough()) { 33 - activeView = view 34 - } 35 - } else if (count > 1) { 36 - var mostVisibleView: BlueskyVideoView? = null 37 - var mostVisiblePosition: Rect? = null 30 + if (count == 1) { 31 + val view = views.first() 32 + if (view.isViewableEnough()) { 33 + activeView = view 34 + } 35 + } else if (count > 1) { 36 + var mostVisibleView: BlueskyVideoView? = null 37 + var mostVisiblePosition: Rect? = null 38 38 39 - views.forEach { view -> 40 - if (!view.isViewableEnough()) { 41 - return 42 - } 39 + views.forEach { view -> 40 + if (!view.isViewableEnough()) { 41 + return 42 + } 43 43 44 - val position = view.getPositionOnScreen() ?: return@forEach 45 - val topY = position.centerY() - (position.height() / 2) 44 + val position = view.getPositionOnScreen() ?: return@forEach 45 + val topY = position.centerY() - (position.height() / 2) 46 46 47 - if (topY >= 150) { 48 - if (mostVisiblePosition == null) { 49 - mostVisiblePosition = position 47 + if (topY >= 150) { 48 + if (mostVisiblePosition == null) { 49 + mostVisiblePosition = position 50 + } 51 + 52 + if (position.centerY() <= mostVisiblePosition!!.centerY()) { 53 + mostVisibleView = view 54 + mostVisiblePosition = position 55 + } 56 + } 57 + } 58 + 59 + activeView = mostVisibleView 60 + } 61 + 62 + if (activeView == currentlyActiveView) { 63 + return 50 64 } 51 65 52 - if (position.centerY() <= mostVisiblePosition!!.centerY()) { 53 - mostVisibleView = view 54 - mostVisiblePosition = position 66 + this.clearActiveView() 67 + if (activeView != null) { 68 + this.setActiveView(activeView) 55 69 } 56 - } 57 70 } 58 71 59 - activeView = mostVisibleView 60 - } 72 + private fun clearActiveView() { 73 + currentlyActiveView?.setIsCurrentlyActive(false) 74 + currentlyActiveView = null 75 + } 61 76 62 - if (activeView == currentlyActiveView) { 63 - return 64 - } 65 - 66 - this.clearActiveView() 67 - if (activeView != null) { 68 - this.setActiveView(activeView) 69 - } 70 - } 71 - 72 - private fun clearActiveView() { 73 - currentlyActiveView?.setIsCurrentlyActive(false) 74 - currentlyActiveView = null 75 - } 76 - 77 - private fun setActiveView(view: BlueskyVideoView) { 78 - val didSet = view.setIsCurrentlyActive(true) 79 - if (didSet) { 80 - currentlyActiveView = view 81 - } 82 - } 77 + private fun setActiveView(view: BlueskyVideoView) { 78 + val didSet = view.setIsCurrentlyActive(true) 79 + if (didSet) { 80 + currentlyActiveView = view 81 + } 82 + } 83 83 84 - fun getActiveView(): BlueskyVideoView? { 85 - return currentlyActiveView 84 + fun getActiveView(): BlueskyVideoView? = currentlyActiveView 86 85 } 87 - } 88 - } 86 + }
+9 -9
ios/AudioManagement.swift
··· 9 9 10 10 class AudioManagement { 11 11 static let shared = AudioManagement() 12 - 12 + 13 13 let audioSession = AVAudioSession.sharedInstance() 14 - var prevAudioCategory: AVAudioSession.Category? = nil 14 + var prevAudioCategory: AVAudioSession.Category? 15 15 var prevAudioActive: Bool = false 16 - 16 + 17 17 func setPlayingVideo(_ playing: Bool) { 18 18 if playing { 19 19 self.setAudioCategory(category: .playback) ··· 23 23 self.setAudioActive(false) 24 24 } 25 25 } 26 - 26 + 27 27 private func setAudioCategory(category: AVAudioSession.Category) { 28 28 if self.audioSession.category == category { 29 29 return 30 30 } 31 - 31 + 32 32 self.prevAudioCategory = self.audioSession.category 33 33 DispatchQueue.global(qos: .background).async { 34 34 try? AVAudioSession.sharedInstance().setCategory(category) 35 35 } 36 36 } 37 - 37 + 38 38 private func setAudioActive(_ active: Bool) { 39 39 if active == self.prevAudioActive { 40 40 return 41 41 } 42 - 42 + 43 43 if active { 44 44 DispatchQueue.global(qos: .background).async { 45 45 do { 46 46 try AVAudioSession.sharedInstance().setActive(true) 47 47 self.prevAudioActive = !self.prevAudioActive 48 48 } catch { } 49 - 50 - let _ = try? AVAudioSession.sharedInstance().setActive(true) 49 + 50 + _ = try? AVAudioSession.sharedInstance().setActive(true) 51 51 } 52 52 } else { 53 53 DispatchQueue.global(qos: .background).async {
+18 -14
ios/BlueskyVideoModule.swift
··· 1 1 import ExpoModulesCore 2 2 3 3 public class BlueskyVideoModule: Module { 4 - private var wasPlayingPlayer: AVPlayer? = nil 5 - 4 + private var wasPlayingPlayer: AVPlayer? 5 + 6 6 public func definition() -> ModuleDefinition { 7 7 Name("BlueskyVideo") 8 - 8 + 9 9 OnAppEntersForeground { 10 10 self.wasPlayingPlayer?.play() 11 11 self.wasPlayingPlayer = nil 12 12 } 13 - 13 + 14 14 OnAppEntersBackground { 15 15 PlayerManager.shared.allPlayers().forEach { player in 16 16 if player.isPlaying { ··· 20 20 } 21 21 } 22 22 } 23 - 23 + 24 24 AsyncFunction("updateActiveVideoViewAsync") { 25 25 ViewManager.shared.updateActiveView() 26 26 } 27 - 27 + 28 28 View(VideoView.self) { 29 29 Events([ 30 - "onStatusChange", 30 + "onActiveChange", 31 31 "onLoadingChange", 32 - "onError", 33 32 "onMutedChange", 33 + "onStatusChange", 34 34 "onTimeRemainingChange", 35 - "onActiveChange", 35 + "onError" 36 36 ]) 37 - 37 + 38 38 Prop("url") { (view: VideoView, prop: URL) in 39 39 view.url = prop 40 40 } 41 - 41 + 42 42 Prop("autoplay") { (view: VideoView, prop: Bool) in 43 43 view.autoplay = prop 44 44 } 45 - 45 + 46 + Prop("beginMuted") { (view: VideoView, prop: Bool) in 47 + view.beginMuted = prop 48 + } 49 + 46 50 AsyncFunction("togglePlayback") { (view: VideoView) in 47 51 view.togglePlayback() 48 52 } 49 - 53 + 50 54 AsyncFunction("toggleMuted") { (view: VideoView) in 51 55 view.toggleMuted() 52 56 } 53 - 57 + 54 58 AsyncFunction("enterFullscreen") { (view: VideoView) in 55 59 view.enterFullscreen() 56 60 }
+5 -5
ios/Manager.swift
··· 7 7 8 8 class Manager<T: AnyObject> { 9 9 private let objects = NSHashTable<T>.weakObjects() 10 - 10 + 11 11 func add(_ object: T) { 12 12 objects.add(object) 13 13 } 14 - 14 + 15 15 func remove(_ object: T) { 16 16 objects.remove(object) 17 17 } 18 - 18 + 19 19 func count() -> Int { 20 20 return objects.count 21 21 } 22 - 22 + 23 23 func has(_ object: T) -> Bool { 24 24 return objects.contains(object) 25 25 } 26 - 26 + 27 27 func getEnumerator() -> NSEnumerator? { 28 28 return objects.objectEnumerator() 29 29 }
+8 -8
ios/PlayerManager.swift
··· 9 9 10 10 class PlayerManager { 11 11 static let shared = PlayerManager() 12 - 12 + 13 13 private var availablePlayers: [AVPlayer] = [] 14 14 private var usedPlayers: Set<AVPlayer> = [] 15 - 15 + 16 16 func dequeuePlayer() -> AVPlayer { 17 17 if let player = availablePlayers.popLast() { 18 18 self.usedPlayers.insert(player) ··· 24 24 return newPlayer 25 25 } 26 26 } 27 - 27 + 28 28 func recyclePlayer(_ player: AVPlayer) { 29 29 self.resetPlayer(player) 30 30 self.usedPlayers.remove(player) 31 31 self.availablePlayers.append(player) 32 32 } 33 - 33 + 34 34 private func resetPlayer(_ player: AVPlayer) { 35 35 player.replaceCurrentItem(with: nil) 36 36 player.isMuted = true 37 37 player.pause() 38 38 player.seek(to: CMTime.zero) 39 39 } 40 - 40 + 41 41 // MARK: - configuration 42 - 42 + 43 43 private func applyDefaultsToPlayer(_ player: AVPlayer) { 44 44 player.automaticallyWaitsToMinimizeStalling = false 45 45 player.preventsDisplaySleepDuringVideoPlayback = true 46 46 player.isMuted = true 47 - 47 + 48 48 } 49 - 49 + 50 50 func allPlayers() -> [AVPlayer] { 51 51 return Array(self.usedPlayers) + self.availablePlayers 52 52 }
+101 -84
ios/VideoView.swift
··· 2 2 import AVFoundation 3 3 4 4 class VideoView: ExpoView, AVPlayerViewControllerDelegate { 5 - var pViewController: AVPlayerViewController? = nil 6 - var player: AVPlayer? = nil 7 - 5 + private var pViewController: AVPlayerViewController? 6 + private var player: AVPlayer? 7 + private var periodicTimeObserver: Any? 8 + 9 + // props 10 + var autoplay: Bool = true 11 + var url: URL? 12 + var beginMuted = true 13 + 8 14 // controls 9 - var isPlaying: Bool = false { 15 + private var isLoading: Bool = false { 10 16 didSet { 11 - if isPlaying == oldValue { 17 + if isLoading == oldValue { 12 18 return 13 19 } 14 - 15 - self.onStatusChange([ 16 - "status": isPlaying ? "playing" : "paused" 20 + self.onLoadingChange([ 21 + "isLoading": isLoading 17 22 ]) 18 23 } 19 24 } 20 - 21 - var isLoading: Bool = true { 25 + 26 + private var isPlaying: Bool = false { 22 27 didSet { 23 - if isLoading == oldValue { 28 + if isPlaying == oldValue { 24 29 return 25 30 } 26 - self.onLoadingChange([ 27 - "isLoading": isLoading 31 + 32 + self.onStatusChange([ 33 + "status": isPlaying ? "playing" : "paused" 28 34 ]) 29 35 } 30 36 } 31 - 32 - var isViewActive: Bool = false { 37 + 38 + private var isViewActive: Bool = false { 33 39 didSet { 34 40 if isViewActive == oldValue { 35 41 return ··· 39 45 ]) 40 46 } 41 47 } 42 - 43 - var isFullscreen: Bool = false { 48 + 49 + private var isFullscreen: Bool = false { 44 50 didSet { 45 51 if isFullscreen { 46 52 self.pViewController?.showsPlaybackControls = isFullscreen ··· 50 56 } 51 57 } 52 58 } 53 - 54 - // props 55 - var autoplay: Bool = true 56 - var url: URL? = nil 57 - 59 + 58 60 // event handlers 59 - let onStatusChange = EventDispatcher() 60 - let onLoadingChange = EventDispatcher() 61 - let onError = EventDispatcher() 62 - let onMutedChange = EventDispatcher() 63 - let onTimeRemainingChange = EventDispatcher() 64 - let onActiveChange = EventDispatcher() 65 - 66 - // observers 67 - var periodicTimeObserver: Any? = nil 68 - 61 + private let onActiveChange = EventDispatcher() 62 + private let onLoadingChange = EventDispatcher() 63 + private let onMutedChange = EventDispatcher() 64 + private let onStatusChange = EventDispatcher() 65 + private let onTimeRemainingChange = EventDispatcher() 66 + private let onError = EventDispatcher() 67 + 68 + private var enteredFullScreenMuted = true 69 + private var ignoreAutoplay = false 70 + 69 71 required init(appContext: AppContext? = nil) { 70 72 self.pViewController = AVPlayerViewController() 71 73 super.init(appContext: appContext) 72 74 self.clipsToBounds = true 73 75 } 74 - 76 + 75 77 // MARK: - lifecycle 76 - 77 - func playVideo() { 78 + 79 + private func setup() { 78 80 guard let url = url, self.player == nil else { 79 81 return 80 82 } 81 83 84 + self.isLoading = true 85 + 82 86 // Setup the view controller 83 87 let pViewController = AVPlayerViewController() 84 88 pViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] ··· 87 91 pViewController.showsPlaybackControls = false 88 92 pViewController.delegate = self 89 93 pViewController.videoGravity = .resizeAspectFill 90 - 94 + 91 95 // Recycle the current player if there is one 92 96 if let currentPlayer = self.player { 93 97 PlayerManager.shared.recyclePlayer(currentPlayer) 94 98 } 95 - 99 + 96 100 // Get a new player to use 97 101 let player = PlayerManager.shared.dequeuePlayer() 98 - 102 + 99 103 // Add observers to the player 100 104 self.periodicTimeObserver = self.createPeriodicTimeObserver(player) 101 - 105 + 102 106 // Get the player item and add it to the player 103 107 DispatchQueue.global(qos: .background).async { [weak self] in 104 108 let playerItem = AVPlayerItem(url: url) 105 109 playerItem.preferredForwardBufferDuration = 5 106 - 110 + 107 111 DispatchQueue.main.async { 108 112 player.replaceCurrentItem(with: playerItem) 109 113 self?.addObserversToPlayerItem(playerItem) ··· 112 116 113 117 pViewController.player = player 114 118 self.addSubview(pViewController.view) 115 - 119 + 116 120 self.pViewController = pViewController 117 121 self.player = player 118 122 } 119 - 120 - func removeVideo() { 123 + 124 + private func destroy() { 121 125 guard let player = self.player else { 122 126 return 123 127 } 124 128 129 + self.ignoreAutoplay = false 130 + 125 131 // Fire final events 126 132 self.mute() 127 133 self.pause() 128 - self.isLoading = true 129 - 134 + self.isLoading = false 135 + 130 136 // Remove period time observer and nil it 131 137 if let periodicTimeObserver = self.periodicTimeObserver { 132 138 self.player?.removeTimeObserver(periodicTimeObserver) 133 139 self.periodicTimeObserver = nil 134 140 } 135 - 141 + 136 142 // Remove any observers from the player item and nil the item 137 143 if let playerItem = self.player?.currentItem { 138 144 removeObserversFromPlayerItem(playerItem) ··· 141 147 // Recycle the player and nil the player 142 148 PlayerManager.shared.recyclePlayer(player) 143 149 self.player = nil 144 - 150 + 145 151 // Remove the player from the controller 146 152 self.pViewController?.player = nil 147 - 153 + 148 154 // Remove the view controller 149 155 self.pViewController?.view.removeFromSuperview() 150 156 self.pViewController?.removeFromParent() 151 157 self.pViewController = nil 152 158 } 153 - 159 + 154 160 override func willMove(toWindow newWindow: UIWindow?) { 155 161 // Ignore anything that happens whenever we enter fullscreen. It's expected that the view will unmount here 156 162 if self.isFullscreen { 157 163 return 158 164 } 159 - 165 + 160 166 if newWindow == nil { 161 167 ViewManager.shared.remove(self) 162 - self.removeVideo() 168 + self.destroy() 163 169 } else { 164 170 ViewManager.shared.add(self) 165 171 } 166 172 } 167 - 173 + 168 174 deinit { 169 - self.removeVideo() 175 + self.destroy() 170 176 } 171 - 177 + 172 178 // MARK: - observers 173 - 179 + 174 180 @objc func playerDidFinishPlaying(notification: NSNotification) { 175 181 self.player?.seek(to: CMTime.zero) 176 182 self.play() 177 183 } 178 - 184 + 179 185 override func observeValue(forKeyPath keyPath: String?, 180 186 of object: Any?, 181 - change: [NSKeyValueChangeKey : Any]?, 187 + change: [NSKeyValueChangeKey: Any]?, 182 188 context: UnsafeMutableRawPointer?) { 183 - 189 + 184 190 // This shouldn't happen, but just guard nil values 185 191 guard let player = self.player, 186 192 let playerItem = player.currentItem else { 187 193 return 188 194 } 189 - 195 + 190 196 // status changes for the player item, i.e. for loading 191 197 if keyPath == "status" { 192 198 if playerItem.status == AVPlayerItem.Status.readyToPlay { 193 199 self.isLoading = false 194 - if self.autoplay { 200 + if self.autoplay || self.ignoreAutoplay { 195 201 self.play() 196 202 } 197 203 } ··· 203 209 } 204 210 } 205 211 } 206 - 212 + 207 213 func createPeriodicTimeObserver(_ player: AVPlayer) -> Any? { 208 214 let interval = CMTime(seconds: 1, 209 215 preferredTimescale: CMTimeScale(NSEC_PER_SEC)) 210 - 216 + 211 217 return player.addPeriodicTimeObserver(forInterval: interval, 212 218 queue: .main) { [weak self] time in 213 219 guard let duration = self?.player?.currentItem?.duration else { ··· 219 225 ]) 220 226 } 221 227 } 222 - 228 + 223 229 func addObserversToPlayerItem(_ playerItem: AVPlayerItem) { 224 230 NotificationCenter.default.addObserver(self, 225 231 selector: #selector(playerDidFinishPlaying), ··· 227 233 object: playerItem) 228 234 playerItem.addObserver(self, forKeyPath: "status", options: [.old, .new], context: nil) 229 235 } 230 - 236 + 231 237 func removeObserversFromPlayerItem(_ playerItem: AVPlayerItem) { 232 238 NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: playerItem) 233 239 playerItem.removeObserver(self, forKeyPath: "status") 234 240 } 235 - 241 + 236 242 // MARK: - AVPlayerViewControllerDelegate 237 - 243 + 238 244 func playerViewController(_ playerViewController: AVPlayerViewController, 239 245 willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { 240 - coordinator.animate(alongsideTransition: nil) { transitionContext in 246 + coordinator.animate(alongsideTransition: nil) { _ in 241 247 self.isFullscreen = false 242 - self.mute() 248 + if self.enteredFullScreenMuted { 249 + self.mute() 250 + } 243 251 self.play() 244 252 } 245 253 } 246 - 254 + 247 255 // MARK: - visibility 248 - 256 + 249 257 func setIsCurrentlyActive(active: Bool) -> Bool { 250 258 if self.isFullscreen { 251 259 return false ··· 253 261 254 262 self.isViewActive = active 255 263 if active { 256 - self.playVideo() 264 + if self.autoplay { 265 + self.setup() 266 + } 257 267 } else { 258 - self.removeVideo() 268 + self.destroy() 259 269 } 260 270 return true 261 271 } 262 - 272 + 263 273 // MARK: - controls 264 - 274 + 265 275 private func play() { 266 276 self.player?.play() 267 277 self.isPlaying = true 268 278 } 269 - 279 + 270 280 private func pause() { 271 281 self.player?.pause() 272 282 self.isPlaying = false 273 283 } 274 - 284 + 275 285 func togglePlayback() { 276 286 if self.isPlaying { 277 - self.play() 287 + self.pause() 278 288 } else { 279 - self.pause() 289 + if self.player == nil { 290 + ViewManager.shared.setActiveView(self) 291 + self.ignoreAutoplay = true 292 + self.setup() 293 + } else { 294 + self.play() 295 + } 280 296 } 281 297 } 282 - 298 + 283 299 private func mute() { 284 300 AudioManagement.shared.setPlayingVideo(false) 285 301 self.player?.isMuted = true ··· 287 303 "isMuted": true 288 304 ]) 289 305 } 290 - 306 + 291 307 private func unmute() { 292 308 AudioManagement.shared.setPlayingVideo(true) 293 309 self.player?.isMuted = false 294 310 onMutedChange([ 295 - "isMuted": false, 311 + "isMuted": false 296 312 ]) 297 313 } 298 - 314 + 299 315 func toggleMuted() { 300 316 if self.player?.isMuted == true { 301 317 self.unmute() ··· 303 319 self.mute() 304 320 } 305 321 } 306 - 322 + 307 323 func enterFullscreen() { 308 324 guard let pViewController = self.pViewController, 309 325 !isFullscreen else { 310 326 return 311 327 } 312 - 328 + 313 329 let selectorName = "enterFullScreenAnimated:completionHandler:" 314 330 let selectorToForceFullScreenMode = NSSelectorFromString(selectorName) 315 331 316 332 if pViewController.responds(to: selectorToForceFullScreenMode) { 317 333 pViewController.perform(selectorToForceFullScreenMode, with: true, with: nil) 334 + self.enteredFullScreenMuted = self.player?.isMuted ?? true 318 335 self.unmute() 319 336 self.isFullscreen = true 320 337 }
+10 -10
ios/ViewManager.swift
··· 9 9 10 10 class ViewManager: Manager<VideoView> { 11 11 static let shared = ViewManager() 12 - 12 + 13 13 private var currentlyActiveView: VideoView? 14 14 private var screenHeight = UIScreen.main.bounds.height 15 15 private var prevCount = 0 16 - 16 + 17 17 override func add(_ object: VideoView) { 18 18 super.add(object) 19 - 19 + 20 20 if self.prevCount == 0 { 21 21 self.updateActiveView() 22 22 } ··· 27 27 super.remove(object) 28 28 self.prevCount = self.count() 29 29 } 30 - 30 + 31 31 func updateActiveView() { 32 32 DispatchQueue.main.async { 33 33 var activeView: VideoView? ··· 44 44 guard let views = self.getEnumerator() else { 45 45 return 46 46 } 47 - 47 + 48 48 var mostVisibleView: VideoView? 49 49 var mostVisiblePosition: CGRect? 50 50 ··· 52 52 guard let view = view as? VideoView else { 53 53 return 54 54 } 55 - 55 + 56 56 if !view.isViewableEnough() { 57 57 return 58 58 } ··· 87 87 } 88 88 } 89 89 } 90 - 90 + 91 91 private func clearActiveView() { 92 92 if let currentlyActiveView = self.currentlyActiveView { 93 - currentlyActiveView.setIsCurrentlyActive(active: false) 93 + _ = currentlyActiveView.setIsCurrentlyActive(active: false) 94 94 self.currentlyActiveView = nil 95 95 } 96 96 } 97 - 98 - private func setActiveView(_ view: VideoView) { 97 + 98 + func setActiveView(_ view: VideoView) { 99 99 let didUpdate = view.setIsCurrentlyActive(active: true) 100 100 if didUpdate { 101 101 self.currentlyActiveView = view
+2 -2
package-lock.json
··· 1 1 { 2 2 "name": "bluesky-video", 3 - "version": "0.1.1-alpha.11", 3 + "version": "0.1.1-alpha.15", 4 4 "lockfileVersion": 3, 5 5 "requires": true, 6 6 "packages": { 7 7 "": { 8 8 "name": "bluesky-video", 9 - "version": "0.1.1-alpha.11", 9 + "version": "0.1.1-alpha.15", 10 10 "license": "MIT", 11 11 "devDependencies": { 12 12 "@types/react": "^18.0.25",
+1 -1
package.json
··· 1 1 { 2 2 "name": "@haileyok/bluesky-video", 3 - "version": "0.1.1-alpha.11", 3 + "version": "0.1.1-alpha.15", 4 4 "description": "A video player library for Bluesky", 5 5 "main": "build/index.js", 6 6 "types": "build/index.d.ts",
+2 -1
src/BlueskyVideo.types.ts
··· 3 3 export type BlueskyVideoViewProps = { 4 4 url: string 5 5 autoplay: boolean 6 + beginMuted: boolean 7 + 6 8 onActiveChange?: (e: NativeSyntheticEvent<{isActive: boolean}>) => void 7 - 8 9 onLoadingChange?: (e: NativeSyntheticEvent<{isLoading: boolean}>) => void 9 10 onMutedChange?: (e: NativeSyntheticEvent<{isMuted: boolean}>) => void 10 11 onPlayerPress?: () => void