Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 110 additions & 1 deletion android/src/main/java/com/tpstreams/TPStreamsRNPlayerView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ package com.tpstreams

import android.util.Log
import android.widget.FrameLayout
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.events.RCTEventEmitter
import com.tpstreams.player.TPStreamsPlayer
import com.tpstreams.player.TPStreamsPlayerView
import androidx.media3.common.Player
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.PlaybackException

class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context) {

private val playerView: TPStreamsPlayerView = TPStreamsPlayerView(context)
private var player: TPStreamsPlayer? = null
private val reactContext: ReactContext = context

private var videoId: String? = null
private var accessToken: String? = null
Expand All @@ -18,6 +24,31 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)
addView(playerView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
}

// Emit React Native events
private fun emitEvent(eventName: String, data: Map<String, Any>) {
val event = Arguments.createMap()
data.forEach { (key, value) ->
when (value) {
is Int -> event.putInt(key, value)
is Double -> event.putDouble(key, value)
is Boolean -> event.putBoolean(key, value)
is String -> event.putString(key, value)
else -> Log.w("TPStreamsRN", "Unsupported type for event data: ${value::class.java}")
}
}
reactContext.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(id, eventName, event)
}

private fun sendErrorEvent(message: String, code: Int = 0, details: String? = null) {
val event = Arguments.createMap()
event.putString("message", message)
event.putInt("code", code)
details?.let { event.putString("details", it) }
reactContext.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(id, "onError", event)
}

fun setVideoId(videoId: String?) {
this.videoId = videoId
tryCreatePlayer()
Expand All @@ -34,13 +65,91 @@ class TPStreamsRNPlayerView(context: ThemedReactContext) : FrameLayout(context)

try {
player = TPStreamsPlayer.create(context, videoId!!, accessToken!!)

// Add player event listeners
player?.addListener(createPlayerListener())

playerView.player = player
playerView.showController()

// Send initial events
emitEvent("onPlayerStateChanged", mapOf("playbackState" to 0))
emitEvent("onIsPlayingChanged", mapOf("isPlaying" to false))
emitEvent("onPlaybackSpeedChanged", mapOf("speed" to 1.0))
emitEvent("onIsLoadingChanged", mapOf("isLoading" to false))
} catch (e: Exception) {
Log.e("TPStreamsRN", "Error creating player", e)
sendErrorEvent("Error creating player", 1001, e.message)
}
}

private fun createPlayerListener(): Player.Listener {
return object : Player.Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
emitEvent("onPlayerStateChanged", mapOf("playbackState" to playbackState))
}

override fun onIsPlayingChanged(isPlaying: Boolean) {
emitEvent("onIsPlayingChanged", mapOf("isPlaying" to isPlaying))
}

override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) {
emitEvent("onPlaybackSpeedChanged", mapOf("speed" to playbackParameters.speed.toDouble()))
}

override fun onIsLoadingChanged(isLoading: Boolean) {
emitEvent("onIsLoadingChanged", mapOf("isLoading" to isLoading))
}

override fun onPlayerError(error: PlaybackException) {
Log.e("TPStreamsRN", "Player error", error)
sendErrorEvent("Playback error", error.errorCode, error.message)
}
}
}

// Player control methods
fun play() {
player?.play()
}

fun pause() {
player?.pause()
}

fun seekTo(positionMs: Long) {
player?.seekTo(positionMs)
}

fun setPlaybackSpeed(speed: Float) {
player?.setPlaybackSpeed(speed)
}

// Player information methods with event emission
fun getCurrentPosition(): Long {
val position = player?.currentPosition ?: 0L
emitEvent("onCurrentPosition", mapOf("position" to position.toDouble()))
return position
}

fun getDuration(): Long {
val duration = player?.duration ?: 0L
emitEvent("onDuration", mapOf("duration" to duration.toDouble()))
return duration
}

fun isPlaying(): Boolean {
val playing = player?.isPlaying ?: false
emitEvent("onIsPlaying", mapOf("isPlaying" to playing))
return playing
}

fun getPlaybackSpeed(): Float {
val speed = player?.playbackParameters?.speed ?: 1.0f
emitEvent("onPlaybackSpeed", mapOf("speed" to speed.toDouble()))
return speed
}

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
try {
Expand Down
85 changes: 68 additions & 17 deletions android/src/main/java/com/tpstreams/TPStreamsRNPlayerViewManager.kt
Original file line number Diff line number Diff line change
@@ -1,46 +1,97 @@
package com.tpstreams

import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.viewmanagers.TPStreamsRNPlayerViewManagerInterface
import com.facebook.react.viewmanagers.TPStreamsRNPlayerViewManagerDelegate
import com.facebook.react.common.MapBuilder

@ReactModule(name = TPStreamsRNPlayerViewManager.NAME)
class TPStreamsRNPlayerViewManager : SimpleViewManager<TPStreamsRNPlayerView>(),
TPStreamsRNPlayerViewManagerInterface<TPStreamsRNPlayerView> {
private val mDelegate: ViewManagerDelegate<TPStreamsRNPlayerView>

init {
mDelegate = TPStreamsRNPlayerViewManagerDelegate(this)

companion object {
const val NAME = "TPStreamsRNPlayerView"

// Event names
private const val EVENT_CURRENT_POSITION = "onCurrentPosition"
private const val EVENT_DURATION = "onDuration"
private const val EVENT_IS_PLAYING = "onIsPlaying"
private const val EVENT_PLAYBACK_SPEED = "onPlaybackSpeed"
private const val EVENT_PLAYER_STATE_CHANGED = "onPlayerStateChanged"
private const val EVENT_IS_PLAYING_CHANGED = "onIsPlayingChanged"
private const val EVENT_PLAYBACK_SPEED_CHANGED = "onPlaybackSpeedChanged"
private const val EVENT_IS_LOADING_CHANGED = "onIsLoadingChanged"
private const val EVENT_ERROR = "onError"
}

private val mDelegate: ViewManagerDelegate<TPStreamsRNPlayerView> =
TPStreamsRNPlayerViewManagerDelegate(this)

override fun getDelegate(): ViewManagerDelegate<TPStreamsRNPlayerView>? {
return mDelegate
}
override fun getDelegate(): ViewManagerDelegate<TPStreamsRNPlayerView>? = mDelegate

override fun getName(): String {
return NAME
}
override fun getName(): String = NAME

public override fun createViewInstance(context: ThemedReactContext): TPStreamsRNPlayerView {
return TPStreamsRNPlayerView(context)
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
return MapBuilder.builder<String, Any>()
.put(EVENT_CURRENT_POSITION, MapBuilder.of("registrationName", EVENT_CURRENT_POSITION))
.put(EVENT_DURATION, MapBuilder.of("registrationName", EVENT_DURATION))
.put(EVENT_IS_PLAYING, MapBuilder.of("registrationName", EVENT_IS_PLAYING))
.put(EVENT_PLAYBACK_SPEED, MapBuilder.of("registrationName", EVENT_PLAYBACK_SPEED))
.put(EVENT_PLAYER_STATE_CHANGED, MapBuilder.of("registrationName", EVENT_PLAYER_STATE_CHANGED))
.put(EVENT_IS_PLAYING_CHANGED, MapBuilder.of("registrationName", EVENT_IS_PLAYING_CHANGED))
.put(EVENT_PLAYBACK_SPEED_CHANGED, MapBuilder.of("registrationName", EVENT_PLAYBACK_SPEED_CHANGED))
.put(EVENT_IS_LOADING_CHANGED, MapBuilder.of("registrationName", EVENT_IS_LOADING_CHANGED))
.put(EVENT_ERROR, MapBuilder.of("registrationName", EVENT_ERROR))
.build()
}

public override fun createViewInstance(context: ThemedReactContext): TPStreamsRNPlayerView =
TPStreamsRNPlayerView(context)

@ReactProp(name = "videoId")
override fun setVideoId(view: TPStreamsRNPlayerView, videoId: String?) {
view.setVideoId(videoId)
view.setVideoId(videoId)
}

@ReactProp(name = "accessToken")
override fun setAccessToken(view: TPStreamsRNPlayerView, accessToken: String?) {
view.setAccessToken(accessToken)
view.setAccessToken(accessToken)
}

companion object {
const val NAME = "TPStreamsRNPlayerView"
// Command implementations
override fun play(view: TPStreamsRNPlayerView) {
view.play()
}

override fun pause(view: TPStreamsRNPlayerView) {
view.pause()
}

override fun seekTo(view: TPStreamsRNPlayerView, positionMs: Double) {
view.seekTo(positionMs.toLong())
}

override fun setPlaybackSpeed(view: TPStreamsRNPlayerView, speed: Float) {
view.setPlaybackSpeed(speed)
}

override fun getCurrentPosition(view: TPStreamsRNPlayerView) {
view.getCurrentPosition()
}

override fun getDuration(view: TPStreamsRNPlayerView) {
view.getDuration()
}

override fun isPlaying(view: TPStreamsRNPlayerView) {
view.isPlaying()
}

override fun getPlaybackSpeed(view: TPStreamsRNPlayerView) {
view.getPlaybackSpeed()
}
}
Loading