package rui import ( "fmt" "math" "strconv" "strings" ) // Constants which related to media player properties and events const ( // Controls is the constant for "controls" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Controls whether the browser need to provide controls to allow user to control audio playback, volume, seeking and // pause/resume playback. Default value is `false`. // // Supported types: `bool`, `int`, `string`. // // Values: // `true` or `1` or "true", "yes", "on", "1" - The browser will offer controls to allow the user to control audio playback, volume, seeking and pause/resume playback. // `false` or `0` or "false", "no", "off", "0" - No controls will be visible to the end user. // // Usage in `VideoPlayer`: // Whether the browser need to provide controls to allow user to control video playback, volume, seeking and pause/resume // playback. Default value is `false`. // // Supported types: `bool`, `int`, `string`. // // Values: // `true` or `1` or "true", "yes", "on", "1" - The browser will offer controls to allow the user to control video playback, volume, seeking and pause/resume playback. // `false` or `0` or "false", "no", "off", "0" - No controls will be visible to the end user. Controls PropertyName = "controls" // Loop is the constant for "loop" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Controls whether the audio player will play media in a loop. Default value is `false`. // // Supported types: `bool`, `int`, `string`. // // Values: // `true` or `1` or "true", "yes", "on", "1" - The audio player will automatically seek back to the start upon reaching the end of the audio. // `false` or `0` or "false", "no", "off", "0" - Audio player will stop playing when the end of the media file has been reached. // // Usage in `VideoPlayer`: // Controls whether the video player will play media in a loop. Default value is `false`. // // Supported types: `bool`, `int`, `string`. // // Values: // `true` or `1` or "true", "yes", "on", "1" - The video player will automatically seek back to the start upon reaching the end of the video. // `false` or `0` or "false", "no", "off", "0" - Video player will stop playing when the end of the media file has been reached. Loop PropertyName = "loop" // Muted is the constant for "muted" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Controls whether the audio will be initially silenced. Default value is `false`. // // Supported types: `bool`, `int`, `string`. // // Values: // `true` or `1` or "true", "yes", "on", "1" - Audio will be muted. // `false` or `0` or "false", "no", "off", "0" - Audio playing normally. // // Usage in `VideoPlayer`: // Controls whether the video will be initially silenced. Default value is `false`. // // Supported types: `bool`, `int`, `string`. // // Values: // `true` or `1` or "true", "yes", "on", "1" - Video will be muted. // `false` or `0` or "false", "no", "off", "0" - Video playing normally. Muted PropertyName = "muted" // Preload is the constant for "preload" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Property is intended to provide a hint to the browser about what the author thinks will lead to the best user // experience. Default value is different for each browser. // // Supported types: `int`, `string`. // // Values: // `0`(`PreloadNone`) or "none" - Media file must not be pre-loaded. // `1`(`PreloadMetadata`) or "metadata" - Only metadata is preloaded. // `2`(`PreloadAuto`) or "auto" - The entire media file can be downloaded even if the user doesn't have to use it. // // Usage in `VideoPlayer`: // Property is intended to provide a hint to the browser about what the author thinks will lead to the best user // experience. Default value is different for each browser. // // Supported types: `int`, `string`. // // Values: // `0`(`PreloadNone`) or "none" - Media file must not be pre-loaded. // `1`(`PreloadMetadata`) or "metadata" - Only metadata is preloaded. // `2`(`PreloadAuto`) or "auto" - The entire media file can be downloaded even if the user doesn't have to use it. Preload PropertyName = "preload" // AbortEvent is the constant for "abort-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Fired when the resource was not fully loaded, but not as the result of an error. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Fired when the resource was not fully loaded, but not as the result of an error. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. AbortEvent PropertyName = "abort-event" // CanPlayEvent is the constant for "can-play-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the browser can play the media, but estimates that not enough data has been loaded to play the media up to // its end without having to stop for further buffering of content. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the browser can play the media, but estimates that not enough data has been loaded to play the media up to // its end without having to stop for further buffering of content. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. CanPlayEvent PropertyName = "can-play-event" // CanPlayThroughEvent is the constant for "can-play-through-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the browser estimates it can play the media up to its end without stopping for content buffering. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the browser estimates it can play the media up to its end without stopping for content buffering. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. CanPlayThroughEvent PropertyName = "can-play-through-event" // CompleteEvent is the constant for "complete-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the rendering of an OfflineAudioContext has been terminated. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the rendering of an OfflineAudioContext has been terminated. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. CompleteEvent PropertyName = "complete-event" // DurationChangedEvent is the constant for "duration-changed-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the duration attribute has been updated. // // General listener format: // `func(player rui.MediaPlayer, duration float64)`. // // where: // player - Interface of a player which generated this event, // duration - Current duration. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(duration float64)`, // `func()`. // // Usage in `VideoPlayer`: // Occur when the duration attribute has been updated. // // General listener format: // `func(player rui.MediaPlayer, duration float64)`. // // where: // player - Interface of a player which generated this event, // duration - Current duration. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(duration float64)`, // `func()`. DurationChangedEvent PropertyName = "duration-changed-event" // EmptiedEvent is the constant for "emptied-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the media has become empty; for example, this event is sent if the media has already been loaded(or // partially loaded), and the HTMLMediaElement.load method is called to reload it. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the media has become empty; for example, this event is sent if the media has already been loaded(or // partially loaded), and the HTMLMediaElement.load method is called to reload it. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. EmptiedEvent PropertyName = "emptied-event" // EndedEvent is the constant for "ended-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the playback has stopped because the end of the media was reached. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the playback has stopped because the end of the media was reached. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. EndedEvent PropertyName = "ended-event" // LoadedDataEvent is the constant for "loaded-data-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the first frame of the media has finished loading. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the first frame of the media has finished loading. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. LoadedDataEvent PropertyName = "loaded-data-event" // LoadedMetadataEvent is the constant for "loaded-metadata-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the metadata has been loaded. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the metadata has been loaded. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. LoadedMetadataEvent PropertyName = "loaded-metadata-event" // LoadStartEvent is the constant for "load-start-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Fired when the browser has started to load a resource. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Fired when the browser has started to load a resource. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. LoadStartEvent PropertyName = "load-start-event" // PauseEvent is the constant for "pause-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the playback has been paused. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the playback has been paused. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. PauseEvent PropertyName = "pause-event" // PlayEvent is the constant for "play-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the playback has begun. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the playback has begun. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. PlayEvent PropertyName = "play-event" // PlayingEvent is the constant for "playing-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the playback is ready to start after having been paused or delayed due to lack of data. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the playback is ready to start after having been paused or delayed due to lack of data. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. PlayingEvent PropertyName = "playing-event" // ProgressEvent is the constant for "progress-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Fired periodically as the browser loads a resource. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Fired periodically as the browser loads a resource. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. ProgressEvent PropertyName = "progress-event" // RateChangedEvent is the constant for "rate-changed-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the playback rate has changed. // // General listener format: // `func(player rui.MediaPlayer, rate float64)`. // // where: // player - Interface of a player which generated this event, // rate - Playback rate. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(rate float64)`, // `func()`. // // Usage in `VideoPlayer`: // Occur when the playback rate has changed. // // General listener format: // `func(player rui.MediaPlayer, rate float64)`. // // where: // player - Interface of a player which generated this event, // rate - Playback rate. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(rate float64)`, // `func()`. RateChangedEvent PropertyName = "rate-changed-event" // SeekedEvent is the constant for "seeked-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when a seek operation completed. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when a seek operation completed. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. SeekedEvent PropertyName = "seeked-event" // SeekingEvent is the constant for "seeking-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when a seek operation has began. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when a seek operation has began. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. SeekingEvent PropertyName = "seeking-event" // StalledEvent is the constant for "stalled-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the user agent is trying to fetch media data, but data is unexpectedly not forthcoming. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the user agent is trying to fetch media data, but data is unexpectedly not forthcoming. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. StalledEvent PropertyName = "stalled-event" // SuspendEvent is the constant for "suspend-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the media data loading has been suspended. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the media data loading has been suspended. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. SuspendEvent PropertyName = "suspend-event" // TimeUpdateEvent is the constant for "time-update-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the time indicated by the currentTime attribute has been updated. // // General listener format: // `func(player rui.MediaPlayer, time float64)`. // // where: // player - Interface of a player which generated this event, // time - Current time. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(time float64)`, // `func()`. // // Usage in `VideoPlayer`: // Occur when the time indicated by the currentTime attribute has been updated. // // General listener format: // `func(player rui.MediaPlayer, time float64)`. // // where: // player - Interface of a player which generated this event, // time - Current time. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(time float64)`, // `func()`. TimeUpdateEvent PropertyName = "time-update-event" // VolumeChangedEvent is the constant for "volume-changed-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the volume has changed. // // General listener format: // `func(player rui.MediaPlayer, volume float64)`. // // where: // player - Interface of a player which generated this event, // volume - New volume level. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(volume float64)`, // `func()`. // // Usage in `VideoPlayer`: // Occur when the volume has changed. // // General listener format: // `func(player rui.MediaPlayer, volume float64)`. // // where: // player - Interface of a player which generated this event, // volume - New volume level. // // Allowed listener formats: // `func(player rui.MediaPlayer)`, // `func(volume float64)`, // `func()`. VolumeChangedEvent PropertyName = "volume-changed-event" // WaitingEvent is the constant for "waiting-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Occur when the playback has stopped because of a temporary lack of data. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. // // Usage in `VideoPlayer`: // Occur when the playback has stopped because of a temporary lack of data. // // General listener format: // `func(player rui.MediaPlayer)`. // // where: // player - Interface of a player which generated this event. // // Allowed listener formats: // `func()`. WaitingEvent PropertyName = "waiting-event" // PlayerErrorEvent is the constant for "player-error-event" property tag. // // Used by `AudioPlayer`, `VideoPlayer`. // // Usage in `AudioPlayer`: // Fired when the resource could not be loaded due to an error(for example, a network connectivity problem). // // General listener format: // `func(player rui.MediaPlayer, code int, message string)`. // // where: // player - Interface of a player which generated this event, // code - Error code. See below, // message - Error message, // Error codes: // `0`(`PlayerErrorUnknown`) - Unknown error, // `1`(`PlayerErrorAborted`) - Fetching the associated resource was interrupted by a user request, // `2`(`PlayerErrorNetwork`) - Some kind of network error has occurred that prevented the media from successfully ejecting, even though it was previously available, // `3`(`PlayerErrorDecode`) - Although the resource was previously identified as being used, an error occurred while trying to decode the media resource, // `4`(`PlayerErrorSourceNotSupported`) - The associated resource object or media provider was found to be invalid. // // Allowed listener formats: // `func(code int, message string)`, // `func(player rui.MediaPlayer)`, // `func()`. // // Usage in `VideoPlayer`: // Fired when the resource could not be loaded due to an error(for example, a network connectivity problem). // // General listener format: // `func(player rui.MediaPlayer, code int, message string)`. // // where: // player - Interface of a player which generated this event, // code - Error code. See below, // message - Error message, // Error codes: // `0`(`PlayerErrorUnknown`) - Unknown error, // `1`(`PlayerErrorAborted`) - Fetching the associated resource was interrupted by a user request, // `2`(`PlayerErrorNetwork`) - Some kind of network error has occurred that prevented the media from successfully ejecting, even though it was previously available, // `3`(`PlayerErrorDecode`) - Although the resource was previously identified as being used, an error occurred while trying to decode the media resource, // `4`(`PlayerErrorSourceNotSupported`) - The associated resource object or media provider was found to be invalid. // // Allowed listener formats: // `func(code int, message string)`, // `func(player rui.MediaPlayer)`, // `func()`. PlayerErrorEvent PropertyName = "player-error-event" // PreloadNone - value of the view "preload" property: indicates that the audio/video should not be preloaded. PreloadNone = 0 // PreloadMetadata - value of the view "preload" property: indicates that only audio/video metadata (e.g. length) is fetched. PreloadMetadata = 1 // PreloadAuto - value of the view "preload" property: indicates that the whole audio file can be downloaded, // even if the user is not expected to use it. PreloadAuto = 2 // PlayerErrorUnknown - MediaPlayer error code: An unknown error. PlayerErrorUnknown = 0 // PlayerErrorAborted - MediaPlayer error code: The fetching of the associated resource was aborted by the user's request. PlayerErrorAborted = 1 // PlayerErrorNetwork - MediaPlayer error code: Some kind of network error occurred which prevented the media // from being successfully fetched, despite having previously been available. PlayerErrorNetwork = 2 // PlayerErrorDecode - MediaPlayer error code: Despite having previously been determined to be usable, // an error occurred while trying to decode the media resource, resulting in an error. PlayerErrorDecode = 3 // PlayerErrorSourceNotSupported - MediaPlayer error code: The associated resource or media provider object has been found to be unsuitable. PlayerErrorSourceNotSupported = 4 ) // MediaPlayer is a common interface for media player views like [AudioPlayer] and [VideoPlayer]. type MediaPlayer interface { View // Play attempts to begin playback of the media. Play() // Pause will pause playback of the media, if the media is already in a paused state this method will have no effect. Pause() // SetCurrentTime sets the current playback time in seconds. SetCurrentTime(seconds float64) // CurrentTime returns the current playback time in seconds. CurrentTime() float64 // Duration returns the value indicating the total duration of the media in seconds. // If no media data is available, the returned value is NaN. Duration() float64 // SetPlaybackRate sets the rate at which the media is being played back. This is used to implement user controls // for fast forward, slow motion, and so forth. The normal playback rate is multiplied by this value to obtain // the current rate, so a value of 1.0 indicates normal speed. SetPlaybackRate(rate float64) // PlaybackRate returns the rate at which the media is being played back. PlaybackRate() float64 // SetVolume sets the audio volume, from 0.0 (silent) to 1.0 (loudest). SetVolume(volume float64) // Volume returns the audio volume, from 0.0 (silent) to 1.0 (loudest). Volume() float64 // IsEnded function tells whether the media element is ended. IsEnded() bool // IsPaused function tells whether the media element is paused. IsPaused() bool } type mediaPlayerData struct { viewData } // MediaSource represent one media file source type MediaSource struct { // Url of the source Url string // MimeType of the source MimeType string } func (player *mediaPlayerData) init(session Session) { player.viewData.init(session) player.tag = "MediaPlayer" player.set = mediaPlayerSet player.changed = mediaPlayerPropertyChanged } func (player *mediaPlayerData) Focusable() bool { return true } func mediaPlayerSet(view View, tag PropertyName, value any) []PropertyName { switch tag { case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, EmptiedEvent, LoadStartEvent, EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent, ProgressEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent: return setNoParamEventListener[MediaPlayer](view, tag, value) case DurationChangedEvent, RateChangedEvent, TimeUpdateEvent, VolumeChangedEvent: return setViewEventListener[MediaPlayer, float64](view, tag, value) case PlayerErrorEvent: if listeners, ok := valueToPlayerErrorListeners(value); ok { if len(listeners) > 0 { view.setRaw(tag, listeners) } else if view.getRaw(tag) != nil { view.setRaw(tag, nil) } else { return []PropertyName{} } return []PropertyName{tag} } notCompatibleType(tag, value) return nil case Source: return setMediaPlayerSource(view, value) } return viewSet(view, tag, value) } func setMediaPlayerSource(properties Properties, value any) []PropertyName { switch value := value.(type) { case string: src := MediaSource{Url: value, MimeType: ""} properties.setRaw(Source, []MediaSource{src}) case MediaSource: properties.setRaw(Source, []MediaSource{value}) case []MediaSource: properties.setRaw(Source, value) case DataObject: url, ok := value.PropertyValue("src") if !ok || url == "" { invalidPropertyValue(Source, value) return nil } mimeType, _ := value.PropertyValue("mime-type") src := MediaSource{Url: url, MimeType: mimeType} properties.setRaw(Source, []MediaSource{src}) case []DataValue: src := []MediaSource{} for _, val := range value { if val.IsObject() { obj := val.Object() if url, ok := obj.PropertyValue("src"); ok && url != "" { mimeType, _ := obj.PropertyValue("mime-type") src = append(src, MediaSource{Url: url, MimeType: mimeType}) } else { invalidPropertyValue(Source, value) return nil } } else { src = append(src, MediaSource{Url: val.Value(), MimeType: ""}) } } if len(src) == 0 { invalidPropertyValue(Source, value) return nil } properties.setRaw(Source, src) default: notCompatibleType(Source, value) return nil } return []PropertyName{Source} } func valueToPlayerErrorListeners(value any) ([]func(MediaPlayer, int, string), bool) { if value == nil { return nil, true } switch value := value.(type) { case func(MediaPlayer, int, string): return []func(MediaPlayer, int, string){value}, true case func(int, string): fn := func(_ MediaPlayer, code int, message string) { value(code, message) } return []func(MediaPlayer, int, string){fn}, true case func(MediaPlayer): fn := func(player MediaPlayer, _ int, _ string) { value(player) } return []func(MediaPlayer, int, string){fn}, true case func(): fn := func(MediaPlayer, int, string) { value() } return []func(MediaPlayer, int, string){fn}, true case []func(MediaPlayer, int, string): if len(value) == 0 { return nil, true } for _, fn := range value { if fn == nil { return nil, false } } return value, true case []func(int, string): count := len(value) if count == 0 { return nil, true } listeners := make([]func(MediaPlayer, int, string), count) for i, v := range value { if v == nil { return nil, false } listeners[i] = func(_ MediaPlayer, code int, message string) { v(code, message) } } return listeners, true case []func(MediaPlayer): count := len(value) if count == 0 { return nil, true } listeners := make([]func(MediaPlayer, int, string), count) for i, v := range value { if v == nil { return nil, false } listeners[i] = func(player MediaPlayer, _ int, _ string) { v(player) } } return listeners, true case []func(): count := len(value) if count == 0 { return nil, true } listeners := make([]func(MediaPlayer, int, string), count) for i, v := range value { if v == nil { return nil, false } listeners[i] = func(MediaPlayer, int, string) { v() } } return listeners, true case []any: count := len(value) if count == 0 { return nil, true } listeners := make([]func(MediaPlayer, int, string), count) for i, v := range value { if v == nil { return nil, false } switch v := v.(type) { case func(MediaPlayer, int, string): listeners[i] = v case func(int, string): listeners[i] = func(_ MediaPlayer, code int, message string) { v(code, message) } case func(MediaPlayer): listeners[i] = func(player MediaPlayer, _ int, _ string) { v(player) } case func(): listeners[i] = func(MediaPlayer, int, string) { v() } default: return nil, false } } return listeners, true } return nil, false } func mediaPlayerEvents() map[PropertyName]string { return map[PropertyName]string{ AbortEvent: "onabort", CanPlayEvent: "oncanplay", CanPlayThroughEvent: "oncanplaythrough", CompleteEvent: "oncomplete", EmptiedEvent: "onemptied", EndedEvent: "ended", LoadedDataEvent: "onloadeddata", LoadedMetadataEvent: "onloadedmetadata", LoadStartEvent: "onloadstart", PauseEvent: "onpause", PlayEvent: "onplay", PlayingEvent: "onplaying", ProgressEvent: "onprogress", SeekedEvent: "onseeked", SeekingEvent: "onseeking", StalledEvent: "onstalled", SuspendEvent: "onsuspend", WaitingEvent: "onwaiting", } } func mediaPlayerPropertyChanged(view View, tag PropertyName) { session := view.Session() switch tag { case Controls, Loop: value, _ := boolProperty(view, tag, session) if value { session.updateProperty(view.htmlID(), string(tag), value) } else { session.removeProperty(view.htmlID(), string(tag)) } case Muted: value, _ := boolProperty(view, Muted, session) session.callFunc("setMediaMuted", view.htmlID(), value) case Preload: value, _ := enumProperty(view, Preload, session, 0) values := enumProperties[Preload].values session.updateProperty(view.htmlID(), string(Preload), values[value]) case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, EmptiedEvent, EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent, ProgressEvent, LoadStartEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent: if cssTag, ok := mediaPlayerEvents()[tag]; ok { fn := "" if value := view.getRaw(tag); value != nil { if listeners, ok := value.([]func(MediaPlayer)); ok && len(listeners) > 0 { fn = fmt.Sprintf(`viewEvent(this, "%s")`, string(tag)) } } session.updateProperty(view.htmlID(), cssTag, fn) } case TimeUpdateEvent: if value := view.getRaw(tag); value != nil { session.updateProperty(view.htmlID(), "ontimeupdate", "viewTimeUpdatedEvent(this)") } else { session.updateProperty(view.htmlID(), "ontimeupdate", "") } case VolumeChangedEvent: if value := view.getRaw(tag); value != nil { session.updateProperty(view.htmlID(), "onvolumechange", "viewVolumeChangedEvent(this)") } else { session.updateProperty(view.htmlID(), "onvolumechange", "") } case DurationChangedEvent: if value := view.getRaw(tag); value != nil { session.updateProperty(view.htmlID(), "ondurationchange", "viewDurationChangedEvent(this)") } else { session.updateProperty(view.htmlID(), "ondurationchange", "") } case RateChangedEvent: if value := view.getRaw(tag); value != nil { session.updateProperty(view.htmlID(), "onratechange", "viewRateChangedEvent(this)") } else { session.updateProperty(view.htmlID(), "onratechange", "") } case PlayerErrorEvent: if value := view.getRaw(tag); value != nil { session.updateProperty(view.htmlID(), "onerror", "viewErrorEvent(this)") } else { session.updateProperty(view.htmlID(), "onerror", "") } case Source: updateInnerHTML(view.htmlID(), session) default: viewPropertyChanged(view, tag) } } func (player *mediaPlayerData) htmlSubviews(self View, buffer *strings.Builder) { if value := player.getRaw(Source); value != nil { if sources, ok := value.([]MediaSource); ok && len(sources) > 0 { session := player.session for _, src := range sources { if url, ok := session.resolveConstants(src.Url); ok && url != "" { buffer.WriteString(`') } } } } } func (player *mediaPlayerData) htmlProperties(self View, buffer *strings.Builder) { player.viewData.htmlProperties(self, buffer) for _, tag := range []PropertyName{Controls, Loop, Muted, Preload} { if value, _ := boolProperty(player, tag, player.session); value { buffer.WriteRune(' ') buffer.WriteString(string(tag)) } } if value, ok := enumProperty(player, Preload, player.session, 0); ok { values := enumProperties[Preload].values buffer.WriteString(` preload="`) buffer.WriteString(values[value]) buffer.WriteRune('"') } for tag, cssTag := range mediaPlayerEvents() { if value := player.getRaw(tag); value != nil { if listeners, ok := value.([]func(MediaPlayer)); ok && len(listeners) > 0 { buffer.WriteString(` `) buffer.WriteString(cssTag) buffer.WriteString(`="playerEvent(this, '`) buffer.WriteString(string(tag)) buffer.WriteString(`')"`) } } } if value := player.getRaw(TimeUpdateEvent); value != nil { buffer.WriteString(` ontimeupdate="playerTimeUpdatedEvent(this)"`) } if value := player.getRaw(VolumeChangedEvent); value != nil { buffer.WriteString(` onvolumechange="playerVolumeChangedEvent(this)"`) } if value := player.getRaw(DurationChangedEvent); value != nil { buffer.WriteString(` ondurationchange="playerDurationChangedEvent(this)"`) } if value := player.getRaw(RateChangedEvent); value != nil { buffer.WriteString(` onratechange="playerRateChangedEvent(this)"`) } if value := player.getRaw(PlayerErrorEvent); value != nil { buffer.WriteString(` onerror="playerErrorEvent(this)"`) } } func (player *mediaPlayerData) handleCommand(self View, command PropertyName, data DataObject) bool { switch command { case AbortEvent, CanPlayEvent, CanPlayThroughEvent, CompleteEvent, LoadStartEvent, EmptiedEvent, EndedEvent, LoadedDataEvent, LoadedMetadataEvent, PauseEvent, PlayEvent, PlayingEvent, ProgressEvent, SeekedEvent, SeekingEvent, StalledEvent, SuspendEvent, WaitingEvent: if value := player.getRaw(command); value != nil { if listeners, ok := value.([]func(MediaPlayer)); ok { for _, listener := range listeners { listener(player) } } } case TimeUpdateEvent, DurationChangedEvent, RateChangedEvent, VolumeChangedEvent: if value := player.getRaw(command); value != nil { if listeners, ok := value.([]func(MediaPlayer, float64)); ok { time := dataFloatProperty(data, "value") for _, listener := range listeners { listener(player, time) } } } case PlayerErrorEvent: if value := player.getRaw(command); value != nil { if listeners, ok := value.([]func(MediaPlayer, int, string)); ok { code, _ := dataIntProperty(data, "code") message, _ := data.PropertyValue("message") for _, listener := range listeners { listener(player, code, message) } } } } return player.viewData.handleCommand(self, command, data) } func (player *mediaPlayerData) Play() { player.session.callFunc("mediaPlay", player.htmlID()) } func (player *mediaPlayerData) Pause() { player.session.callFunc("mediaPause", player.htmlID()) } func (player *mediaPlayerData) SetCurrentTime(seconds float64) { player.session.callFunc("mediaSetSetCurrentTime", player.htmlID(), seconds) } func (player *mediaPlayerData) getFloatPlayerProperty(tag string) (float64, bool) { value := player.session.htmlPropertyValue(player.htmlID(), tag) if value != "" { result, err := strconv.ParseFloat(value, 32) if err == nil { return result, true } ErrorLog(err.Error()) } return 0, false } func (player *mediaPlayerData) CurrentTime() float64 { if result, ok := player.getFloatPlayerProperty("currentTime"); ok { return result } return 0 } func (player *mediaPlayerData) Duration() float64 { if result, ok := player.getFloatPlayerProperty("duration"); ok { return result } return 0 } func (player *mediaPlayerData) SetPlaybackRate(rate float64) { player.session.callFunc("mediaSetPlaybackRate", player.htmlID(), rate) } func (player *mediaPlayerData) PlaybackRate() float64 { if result, ok := player.getFloatPlayerProperty("playbackRate"); ok { return result } return 1 } func (player *mediaPlayerData) SetVolume(volume float64) { if volume >= 0 && volume <= 1 { player.session.callFunc("mediaSetVolume", player.htmlID(), volume) } } func (player *mediaPlayerData) Volume() float64 { if result, ok := player.getFloatPlayerProperty("volume"); ok { return result } return 1 } func (player *mediaPlayerData) getBoolPlayerProperty(tag string) (bool, bool) { switch value := player.session.htmlPropertyValue(player.htmlID(), tag); strings.ToLower(value) { case "0", "false", "off": return false, true case "1", "true", "on": return false, true } return false, false } func (player *mediaPlayerData) IsEnded() bool { if result, ok := player.getBoolPlayerProperty("ended"); ok { return result } return false } func (player *mediaPlayerData) IsPaused() bool { if result, ok := player.getBoolPlayerProperty("paused"); ok { return result } return false } // MediaPlayerPlay attempts to begin playback of the media. func MediaPlayerPlay(view View, playerID string) { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { player.Play() } else { ErrorLog(`The found View is not MediaPlayer`) } } // MediaPlayerPause will pause playback of the media, if the media is already in a paused state this method will have no effect. func MediaPlayerPause(view View, playerID string) { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { player.Pause() } else { ErrorLog(`The found View is not MediaPlayer`) } } // SetMediaPlayerCurrentTime sets the current playback time in seconds. func SetMediaPlayerCurrentTime(view View, playerID string, seconds float64) { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { player.SetCurrentTime(seconds) } else { ErrorLog(`The found View is not MediaPlayer`) } } // MediaPlayerCurrentTime returns the current playback time in seconds. func MediaPlayerCurrentTime(view View, playerID string) float64 { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { return player.CurrentTime() } ErrorLog(`The found View is not MediaPlayer`) return 0 } // MediaPlayerDuration returns the value indicating the total duration of the media in seconds. // If no media data is available, the returned value is NaN. func MediaPlayerDuration(view View, playerID string) float64 { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { return player.Duration() } ErrorLog(`The found View is not MediaPlayer`) return math.NaN() } // SetVolume sets the audio volume, from 0.0 (silent) to 1.0 (loudest). func SetMediaPlayerVolume(view View, playerID string, volume float64) { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { player.SetVolume(volume) } else { ErrorLog(`The found View is not MediaPlayer`) } } // Volume returns the audio volume, from 0.0 (silent) to 1.0 (loudest). func MediaPlayerVolume(view View, playerID string) float64 { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { return player.Volume() } ErrorLog(`The found View is not MediaPlayer`) return 1 } // SetMediaPlayerPlaybackRate sets the rate at which the media is being played back. This is used to implement user controls // for fast forward, slow motion, and so forth. The normal playback rate is multiplied by this value to obtain // the current rate, so a value of 1.0 indicates normal speed. func SetMediaPlayerPlaybackRate(view View, playerID string, rate float64) { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { player.SetPlaybackRate(rate) } else { ErrorLog(`The found View is not MediaPlayer`) } } // MediaPlayerPlaybackRate returns the rate at which the media is being played back. func MediaPlayerPlaybackRate(view View, playerID string) float64 { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { return player.PlaybackRate() } ErrorLog(`The found View is not MediaPlayer`) return 1 } // IsMediaPlayerEnded function tells whether the media element is ended. func IsMediaPlayerEnded(view View, playerID string) bool { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { return player.IsEnded() } ErrorLog(`The found View is not MediaPlayer`) return false } // IsMediaPlayerPaused function tells whether the media element is paused. func IsMediaPlayerPaused(view View, playerID string) bool { if playerID != "" { view = ViewByID(view, playerID) } if player, ok := view.(MediaPlayer); ok { return player.IsPaused() } ErrorLog(`The found View is not MediaPlayer`) return false }