mirror of https://github.com/anoshenko/rui.git
1257 lines
34 KiB
Go
1257 lines
34 KiB
Go
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.
|
|
//
|
|
// 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, 1, "true", "yes", "on", "1" - The browser will offer controls to allow the user to control audio playback, volume, seeking and pause/resume playback.
|
|
// - false, 0, "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.
|
|
//
|
|
// Controls whether the audio player will play media in a loop. Default value is false.
|
|
//
|
|
// Supported types: bool, int, string.
|
|
//
|
|
// Values:
|
|
// - true, 1, "true", "yes", "on", "1" - The audio player will automatically seek back to the start upon reaching the end of the audio.
|
|
// - false, 0, "false", "no", "off", "0" - Audio 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.
|
|
//
|
|
// Controls whether the audio will be initially silenced. Default value is false.
|
|
//
|
|
// Supported types: bool, int, string.
|
|
//
|
|
// Values:
|
|
// - true, 1, "true", "yes", "on", "1" - Audio will be muted.
|
|
// - false, 0, "false", "no", "off", "0" - Audio playing normally.
|
|
Muted PropertyName = "muted"
|
|
|
|
// Preload is the constant for "preload" property tag.
|
|
//
|
|
// Used by AudioPlayer, 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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 = player.setFunc
|
|
player.changed = player.propertyChanged
|
|
}
|
|
|
|
func (player *mediaPlayerData) Focusable() bool {
|
|
return true
|
|
}
|
|
|
|
func (player *mediaPlayerData) setFunc(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 setNoArgEventListener[MediaPlayer](player, tag, value)
|
|
|
|
case DurationChangedEvent, RateChangedEvent, TimeUpdateEvent, VolumeChangedEvent:
|
|
|
|
return setOneArgEventListener[MediaPlayer, float64](player, tag, value)
|
|
|
|
case PlayerErrorEvent:
|
|
if listeners, ok := valueToPlayerErrorListeners(value); ok {
|
|
return setArrayPropertyValue(player, tag, listeners)
|
|
}
|
|
notCompatibleType(tag, value)
|
|
return nil
|
|
|
|
case Source:
|
|
return setMediaPlayerSource(player, value)
|
|
}
|
|
|
|
return player.viewData.setFunc(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 (player *mediaPlayerData) propertyChanged(tag PropertyName) {
|
|
session := player.Session()
|
|
|
|
switch tag {
|
|
case Controls, Loop:
|
|
value, _ := boolProperty(player, tag, session)
|
|
if value {
|
|
session.updateProperty(player.htmlID(), string(tag), value)
|
|
} else {
|
|
session.removeProperty(player.htmlID(), string(tag))
|
|
}
|
|
|
|
case Muted:
|
|
value, _ := boolProperty(player, Muted, session)
|
|
session.callFunc("setMediaMuted", player.htmlID(), value)
|
|
|
|
case Preload:
|
|
value, _ := enumProperty(player, Preload, session, 0)
|
|
values := enumProperties[Preload].values
|
|
session.updateProperty(player.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 := player.getRaw(tag); value != nil {
|
|
if listeners, ok := value.([]func(MediaPlayer)); ok && len(listeners) > 0 {
|
|
fn = fmt.Sprintf(`viewEvent(this, "%s")`, string(tag))
|
|
}
|
|
}
|
|
session.updateProperty(player.htmlID(), cssTag, fn)
|
|
}
|
|
|
|
case TimeUpdateEvent:
|
|
if value := player.getRaw(tag); value != nil {
|
|
session.updateProperty(player.htmlID(), "ontimeupdate", "viewTimeUpdatedEvent(this)")
|
|
} else {
|
|
session.updateProperty(player.htmlID(), "ontimeupdate", "")
|
|
}
|
|
|
|
case VolumeChangedEvent:
|
|
if value := player.getRaw(tag); value != nil {
|
|
session.updateProperty(player.htmlID(), "onvolumechange", "viewVolumeChangedEvent(this)")
|
|
} else {
|
|
session.updateProperty(player.htmlID(), "onvolumechange", "")
|
|
}
|
|
|
|
case DurationChangedEvent:
|
|
if value := player.getRaw(tag); value != nil {
|
|
session.updateProperty(player.htmlID(), "ondurationchange", "viewDurationChangedEvent(this)")
|
|
} else {
|
|
session.updateProperty(player.htmlID(), "ondurationchange", "")
|
|
}
|
|
|
|
case RateChangedEvent:
|
|
if value := player.getRaw(tag); value != nil {
|
|
session.updateProperty(player.htmlID(), "onratechange", "viewRateChangedEvent(this)")
|
|
} else {
|
|
session.updateProperty(player.htmlID(), "onratechange", "")
|
|
}
|
|
|
|
case PlayerErrorEvent:
|
|
if value := player.getRaw(tag); value != nil {
|
|
session.updateProperty(player.htmlID(), "onerror", "viewErrorEvent(this)")
|
|
} else {
|
|
session.updateProperty(player.htmlID(), "onerror", "")
|
|
}
|
|
|
|
case Source:
|
|
updateInnerHTML(player.htmlID(), session)
|
|
|
|
default:
|
|
player.viewData.propertyChanged(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(`<source src="`)
|
|
buffer.WriteString(url)
|
|
buffer.WriteRune('"')
|
|
if mime, ok := session.resolveConstants(src.MimeType); ok && mime != "" {
|
|
buffer.WriteString(` type="`)
|
|
buffer.WriteString(mime)
|
|
buffer.WriteRune('"')
|
|
}
|
|
buffer.WriteRune('>')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|