node build fixed
This commit is contained in:
414
seanime-2.9.10/internal/nativeplayer/events.go
Normal file
414
seanime-2.9.10/internal/nativeplayer/events.go
Normal file
@@ -0,0 +1,414 @@
|
||||
package nativeplayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"seanime/internal/mkvparser"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type ServerEvent string
|
||||
|
||||
const (
|
||||
ServerEventOpenAndAwait ServerEvent = "open-and-await"
|
||||
ServerEventWatch ServerEvent = "watch"
|
||||
ServerEventSubtitleEvent ServerEvent = "subtitle-event"
|
||||
ServerEventSetTracks ServerEvent = "set-tracks"
|
||||
ServerEventPause ServerEvent = "pause"
|
||||
ServerEventResume ServerEvent = "resume"
|
||||
ServerEventSeek ServerEvent = "seek"
|
||||
ServerEventError ServerEvent = "error"
|
||||
ServerEventAddSubtitleTrack ServerEvent = "add-subtitle-track"
|
||||
ServerEventTerminate ServerEvent = "terminate"
|
||||
)
|
||||
|
||||
// OpenAndAwait opens the player and waits for the client to send the watch event.
|
||||
func (p *NativePlayer) OpenAndAwait(clientId string, loadingState string) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventOpenAndAwait), loadingState)
|
||||
}
|
||||
|
||||
// Watch sends the watch event to the client.
|
||||
func (p *NativePlayer) Watch(clientId string, playbackInfo *PlaybackInfo) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventWatch), playbackInfo, true)
|
||||
}
|
||||
|
||||
// SubtitleEvent sends the subtitle event to the client.
|
||||
func (p *NativePlayer) SubtitleEvent(clientId string, event *mkvparser.SubtitleEvent) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventSubtitleEvent), event, true)
|
||||
}
|
||||
|
||||
// SetTracks sends the set tracks event to the client.
|
||||
func (p *NativePlayer) SetTracks(clientId string, tracks []*mkvparser.TrackInfo) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventSetTracks), tracks)
|
||||
}
|
||||
|
||||
// Pause sends the pause event to the client.
|
||||
func (p *NativePlayer) Pause(clientId string) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventPause), nil)
|
||||
}
|
||||
|
||||
// Resume sends the resume event to the client.
|
||||
func (p *NativePlayer) Resume(clientId string) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventResume), nil)
|
||||
}
|
||||
|
||||
// Seek sends the seek event to the client.
|
||||
func (p *NativePlayer) Seek(clientId string, time float64) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventSeek), time)
|
||||
}
|
||||
|
||||
// Error stops the playback and displays an error message.
|
||||
func (p *NativePlayer) Error(clientId string, err error) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventError), struct {
|
||||
Error string `json:"error"`
|
||||
}{
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// AddSubtitleTrack sends the subtitle track added event to the client.
|
||||
func (p *NativePlayer) AddSubtitleTrack(clientId string, track *mkvparser.TrackInfo) {
|
||||
p.sendPlayerEventTo(clientId, string(ServerEventAddSubtitleTrack), track)
|
||||
}
|
||||
|
||||
// Stop emits a VideoTerminatedEvent to all subscribers.
|
||||
// It should only be called by a module.
|
||||
func (p *NativePlayer) Stop() {
|
||||
p.logger.Debug().Msg("nativeplayer: Stopping playback, notifying subscribers")
|
||||
p.notifySubscribers(&VideoTerminatedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: p.playbackStatus.ClientId},
|
||||
})
|
||||
p.sendPlayerEvent(string(ServerEventTerminate), nil)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Client Events
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type ClientEvent string
|
||||
|
||||
const (
|
||||
PlayerEventVideoPaused ClientEvent = "video-paused"
|
||||
PlayerEventVideoResumed ClientEvent = "video-resumed"
|
||||
PlayerEventVideoCompleted ClientEvent = "video-completed"
|
||||
PlayerEventVideoEnded ClientEvent = "video-ended"
|
||||
PlayerEventVideoSeeked ClientEvent = "video-seeked"
|
||||
PlayerEventVideoError ClientEvent = "video-error"
|
||||
PlayerEventVideoLoadedMetadata ClientEvent = "loaded-metadata"
|
||||
PlayerEventSubtitleFileUploaded ClientEvent = "subtitle-file-uploaded"
|
||||
PlayerEventVideoTerminated ClientEvent = "video-terminated"
|
||||
PlayerEventVideoTimeUpdate ClientEvent = "video-time-update"
|
||||
)
|
||||
|
||||
type (
|
||||
// PlayerEvent is an event coming from the client player.
|
||||
PlayerEvent struct {
|
||||
ClientId string `json:"clientId"`
|
||||
Type ClientEvent `json:"type"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
VideoEvent interface {
|
||||
GetClientId() string
|
||||
}
|
||||
BaseVideoEvent struct {
|
||||
ClientId string `json:"clientId"`
|
||||
}
|
||||
VideoPausedEvent struct {
|
||||
BaseVideoEvent
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
VideoResumedEvent struct {
|
||||
BaseVideoEvent
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
VideoEndedEvent struct {
|
||||
BaseVideoEvent
|
||||
AutoNext bool `json:"autoNext"`
|
||||
}
|
||||
VideoErrorEvent struct {
|
||||
BaseVideoEvent
|
||||
Error string `json:"error"`
|
||||
}
|
||||
VideoSeekedEvent struct {
|
||||
BaseVideoEvent
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
VideoStatusEvent struct {
|
||||
BaseVideoEvent
|
||||
Status PlaybackStatus `json:"status"`
|
||||
}
|
||||
VideoLoadedMetadataEvent struct {
|
||||
BaseVideoEvent
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
SubtitleFileUploadedEvent struct {
|
||||
BaseVideoEvent
|
||||
Filename string `json:"filename"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
VideoTerminatedEvent struct {
|
||||
BaseVideoEvent
|
||||
}
|
||||
VideoCompletedEvent struct {
|
||||
BaseVideoEvent
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
)
|
||||
|
||||
// Client event payloads
|
||||
type (
|
||||
videoStartedPayload struct {
|
||||
Url string `json:"url"`
|
||||
Paused bool `json:"paused"`
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
videoPausedPayload struct {
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
videoResumedPayload struct {
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
videoLoadedMetadataPayload struct {
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
videoSeekedPayload struct {
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
subtitleFileUploadedPayload struct {
|
||||
Filename string `json:"filename"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
videoErrorPayload struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
videoEndedPayload struct {
|
||||
AutoNext bool `json:"autoNext"`
|
||||
}
|
||||
videoTerminatedPayload struct {
|
||||
}
|
||||
videoTimeUpdatePayload struct {
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
Paused bool `json:"paused"`
|
||||
}
|
||||
videoCompletedPayload struct {
|
||||
CurrentTime float64 `json:"currentTime"`
|
||||
Duration float64 `json:"duration"`
|
||||
}
|
||||
)
|
||||
|
||||
// listenToPlayerEvents listens to client events and notifies subscribers.
|
||||
func (p *NativePlayer) listenToPlayerEvents() {
|
||||
// Start a goroutine to listen to native player events
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
// Listen to native player events from the client
|
||||
case clientEvent := <-p.clientPlayerEventSubscriber.Channel:
|
||||
playerEvent := &PlayerEvent{}
|
||||
marshaled, _ := json.Marshal(clientEvent.Payload)
|
||||
// Unmarshal the player event
|
||||
if err := json.Unmarshal(marshaled, &playerEvent); err == nil {
|
||||
// Handle events
|
||||
switch playerEvent.Type {
|
||||
|
||||
// case PlayerEventVideoStarted:
|
||||
// p.setPlaybackStatus(func() {
|
||||
// event := &videoStartedPayload{}
|
||||
// if err := playerEvent.UnmarshalAs(&event); err != nil {
|
||||
// p.notifySubscribers(&VideoStartedEvent{
|
||||
// BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
case PlayerEventVideoPaused:
|
||||
payload := &videoPausedPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
p.playbackStatus.Paused = true
|
||||
p.playbackStatus.CurrentTime = payload.CurrentTime
|
||||
p.playbackStatus.Duration = payload.Duration
|
||||
})
|
||||
p.notifySubscribers(&VideoPausedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
CurrentTime: payload.CurrentTime,
|
||||
Duration: payload.Duration,
|
||||
})
|
||||
p.notifySubscribers(&VideoStatusEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
Status: *p.playbackStatus,
|
||||
})
|
||||
}
|
||||
case PlayerEventVideoResumed:
|
||||
payload := &videoResumedPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
p.playbackStatus.Paused = false
|
||||
p.playbackStatus.CurrentTime = payload.CurrentTime
|
||||
p.playbackStatus.Duration = payload.Duration
|
||||
})
|
||||
p.notifySubscribers(&VideoResumedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
CurrentTime: payload.CurrentTime,
|
||||
Duration: payload.Duration,
|
||||
})
|
||||
p.notifySubscribers(&VideoStatusEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
Status: *p.playbackStatus,
|
||||
})
|
||||
}
|
||||
case PlayerEventVideoCompleted:
|
||||
payload := &videoCompletedPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
p.playbackStatus.CurrentTime = payload.CurrentTime
|
||||
p.playbackStatus.Duration = payload.Duration
|
||||
})
|
||||
p.notifySubscribers(&VideoCompletedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
CurrentTime: payload.CurrentTime,
|
||||
Duration: payload.Duration,
|
||||
})
|
||||
}
|
||||
case PlayerEventVideoEnded:
|
||||
payload := &videoEndedPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
})
|
||||
p.notifySubscribers(&VideoEndedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
AutoNext: payload.AutoNext,
|
||||
})
|
||||
}
|
||||
case PlayerEventVideoError:
|
||||
payload := &videoErrorPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
})
|
||||
p.notifySubscribers(&VideoErrorEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
Error: payload.Error,
|
||||
})
|
||||
}
|
||||
case PlayerEventVideoSeeked:
|
||||
payload := &videoSeekedPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
})
|
||||
if p.seekedEventCancelFunc != nil {
|
||||
p.seekedEventCancelFunc()
|
||||
}
|
||||
var ctx context.Context
|
||||
ctx, p.seekedEventCancelFunc = context.WithCancel(context.Background())
|
||||
// Debounce the event
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
}
|
||||
if p.seekedEventCancelFunc != nil {
|
||||
p.seekedEventCancelFunc()
|
||||
p.seekedEventCancelFunc = nil
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(time.Millisecond * 150):
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.CurrentTime = payload.CurrentTime
|
||||
p.playbackStatus.Duration = payload.Duration
|
||||
})
|
||||
p.notifySubscribers(&VideoSeekedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
CurrentTime: payload.CurrentTime,
|
||||
Duration: payload.Duration,
|
||||
})
|
||||
return
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
// Log error: util.Logger.Error().Err(err).Msg("nativeplayer: Failed to unmarshal video seeked payload")
|
||||
}
|
||||
case PlayerEventVideoLoadedMetadata:
|
||||
payload := &videoLoadedMetadataPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
p.playbackStatus.CurrentTime = payload.CurrentTime
|
||||
p.playbackStatus.Duration = payload.Duration
|
||||
})
|
||||
p.notifySubscribers(&VideoLoadedMetadataEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
CurrentTime: payload.CurrentTime,
|
||||
Duration: payload.Duration,
|
||||
})
|
||||
}
|
||||
case PlayerEventSubtitleFileUploaded:
|
||||
payload := &subtitleFileUploadedPayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
})
|
||||
p.notifySubscribers(&SubtitleFileUploadedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
Filename: payload.Filename,
|
||||
Content: payload.Content,
|
||||
})
|
||||
}
|
||||
case PlayerEventVideoTerminated:
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
})
|
||||
p.notifySubscribers(&VideoTerminatedEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
})
|
||||
case PlayerEventVideoTimeUpdate:
|
||||
payload := &videoTimeUpdatePayload{}
|
||||
if err := playerEvent.UnmarshalAs(&payload); err == nil {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus.ClientId = playerEvent.ClientId
|
||||
p.playbackStatus.CurrentTime = payload.CurrentTime
|
||||
p.playbackStatus.Duration = payload.Duration
|
||||
p.playbackStatus.Paused = payload.Paused
|
||||
})
|
||||
p.notifySubscribers(&VideoStatusEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{ClientId: playerEvent.ClientId},
|
||||
Status: *p.playbackStatus,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Events returns the event channel for the subscriber.
|
||||
func (s *Subscriber) Events() <-chan VideoEvent {
|
||||
return s.eventCh
|
||||
}
|
||||
|
||||
func (e *PlayerEvent) UnmarshalAs(dest interface{}) error {
|
||||
marshaled, _ := json.Marshal(e.Payload)
|
||||
return json.Unmarshal(marshaled, dest)
|
||||
}
|
||||
|
||||
func (e *BaseVideoEvent) GetClientId() string {
|
||||
return e.ClientId
|
||||
}
|
||||
167
seanime-2.9.10/internal/nativeplayer/nativeplayer.go
Normal file
167
seanime-2.9.10/internal/nativeplayer/nativeplayer.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package nativeplayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"seanime/internal/api/anilist"
|
||||
"seanime/internal/events"
|
||||
"seanime/internal/library/anime"
|
||||
"seanime/internal/mkvparser"
|
||||
"seanime/internal/util/result"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/samber/mo"
|
||||
)
|
||||
|
||||
type StreamType string
|
||||
|
||||
const (
|
||||
StreamTypeTorrent StreamType = "torrent"
|
||||
StreamTypeFile StreamType = "localfile"
|
||||
StreamTypeDebrid StreamType = "debrid"
|
||||
)
|
||||
|
||||
type (
|
||||
PlaybackInfo struct {
|
||||
ID string `json:"id"`
|
||||
StreamType StreamType `json:"streamType"`
|
||||
MimeType string `json:"mimeType"` // e.g. "video/mp4", "video/webm"
|
||||
StreamUrl string `json:"streamUrl"` // URL of the stream
|
||||
ContentLength int64 `json:"contentLength"` // Size of the stream in bytes
|
||||
MkvMetadata *mkvparser.Metadata `json:"mkvMetadata,omitempty"` // nil if not ebml
|
||||
EntryListData *anime.EntryListData `json:"entryListData,omitempty"` // nil if not in list
|
||||
Episode *anime.Episode `json:"episode"`
|
||||
Media *anilist.BaseAnime `json:"media"`
|
||||
|
||||
MkvMetadataParser mo.Option[*mkvparser.MetadataParser] `json:"-"`
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
// NativePlayer is the built-in HTML5 video player in Seanime.
|
||||
// There can only be one instance of this player at a time.
|
||||
NativePlayer struct {
|
||||
wsEventManager events.WSEventManagerInterface
|
||||
clientPlayerEventSubscriber *events.ClientEventSubscriber
|
||||
|
||||
playbackStatusMu sync.RWMutex
|
||||
playbackStatus *PlaybackStatus
|
||||
|
||||
seekedEventCancelFunc context.CancelFunc
|
||||
|
||||
subscribers *result.Map[string, *Subscriber]
|
||||
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
PlaybackStatus struct {
|
||||
ClientId string
|
||||
Url string
|
||||
Paused bool
|
||||
CurrentTime float64
|
||||
Duration float64
|
||||
}
|
||||
|
||||
// Subscriber listens to the player events
|
||||
Subscriber struct {
|
||||
eventCh chan VideoEvent
|
||||
}
|
||||
|
||||
NewNativePlayerOptions struct {
|
||||
WsEventManager events.WSEventManagerInterface
|
||||
Logger *zerolog.Logger
|
||||
}
|
||||
)
|
||||
|
||||
// New returns a new instance of NativePlayer.
|
||||
func New(options NewNativePlayerOptions) *NativePlayer {
|
||||
np := &NativePlayer{
|
||||
playbackStatus: &PlaybackStatus{},
|
||||
wsEventManager: options.WsEventManager,
|
||||
clientPlayerEventSubscriber: options.WsEventManager.SubscribeToClientNativePlayerEvents("nativeplayer"),
|
||||
subscribers: result.NewResultMap[string, *Subscriber](),
|
||||
logger: options.Logger,
|
||||
}
|
||||
|
||||
np.listenToPlayerEvents()
|
||||
|
||||
return np
|
||||
}
|
||||
|
||||
// sendPlayerEventTo sends an event of type events.NativePlayerEventType to the client.
|
||||
func (p *NativePlayer) sendPlayerEventTo(clientId string, t string, payload interface{}, noLog ...bool) {
|
||||
p.wsEventManager.SendEventTo(clientId, string(events.NativePlayerEventType), struct {
|
||||
Type string `json:"type"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}{
|
||||
Type: t,
|
||||
Payload: payload,
|
||||
}, noLog...)
|
||||
}
|
||||
|
||||
func (p *NativePlayer) sendPlayerEvent(t string, payload interface{}) {
|
||||
p.wsEventManager.SendEvent(string(events.NativePlayerEventType), struct {
|
||||
Type string `json:"type"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}{
|
||||
Type: t,
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Subscribe lets other modules subscribe to the native player events
|
||||
func (p *NativePlayer) Subscribe(id string) *Subscriber {
|
||||
subscriber := &Subscriber{
|
||||
eventCh: make(chan VideoEvent, 10),
|
||||
}
|
||||
p.subscribers.Set(id, subscriber)
|
||||
|
||||
return subscriber
|
||||
}
|
||||
|
||||
// Unsubscribe removes a subscriber from the player.
|
||||
func (p *NativePlayer) Unsubscribe(id string) {
|
||||
p.subscribers.Delete(id)
|
||||
}
|
||||
|
||||
func (p *NativePlayer) notifySubscribers(event VideoEvent) {
|
||||
p.subscribers.Range(func(id string, subscriber *Subscriber) bool {
|
||||
select {
|
||||
case subscriber.eventCh <- event:
|
||||
default:
|
||||
// If the channel is full, skip sending the event
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GetPlaybackStatus returns the current playback status of the player.
|
||||
func (p *NativePlayer) GetPlaybackStatus() *PlaybackStatus {
|
||||
p.playbackStatusMu.RLock()
|
||||
defer p.playbackStatusMu.RUnlock()
|
||||
return p.playbackStatus
|
||||
}
|
||||
|
||||
func (p *NativePlayer) SetPlaybackStatus(status *PlaybackStatus) {
|
||||
p.setPlaybackStatus(func() {
|
||||
p.playbackStatus = status
|
||||
})
|
||||
}
|
||||
|
||||
// setPlaybackStatus sets the current playback status of the player
|
||||
// and notifies all subscribers of the change.
|
||||
func (p *NativePlayer) setPlaybackStatus(do func()) {
|
||||
p.playbackStatusMu.Lock()
|
||||
defer p.playbackStatusMu.Unlock()
|
||||
do()
|
||||
p.notifySubscribers(&VideoStatusEvent{
|
||||
BaseVideoEvent: BaseVideoEvent{
|
||||
ClientId: p.playbackStatus.ClientId,
|
||||
},
|
||||
Status: *p.playbackStatus,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user