Images
Video
Video files and streams with sound, played by the system's media engine, and animated GIFs and PNGs, as trailers, clips behind the content or previews.
ui.Video shows a video that plays: a trailer with sound and controls, a
clip looping behind the content, as a launcher's library or a music
player's hero shows one, or a preview in a grid. It shows a
ui.VideoPlayer, and is laid out, fitted and rounded as an
Image is.
A player comes from one of two places:
ui/videoopens video files and streams (MP4 and MOV with H.264 and AAC everywhere, more where the system has the codecs, HLS streams on macOS, Windows and browsers) with the system's own media framework, which decodes them in hardware and plays their sound. Its pictures reach the window's renderer where the decoder left them, on the GPU.- A decoder in Go:
ui.NewGIFDecoderandui.NewAPNGDecoderplay animated GIFs and PNGs on every system, without sound, as backgrounds that should never depend on the system; aui.VideoDecoderof your own plays another format.
import "github.com/egoist/mygo/ui/video"
//go:embed trailer.mp4
var trailerMP4 []byte
app.trailer, err = video.OpenData(trailerMP4) // or video.Open(path), video.OpenURL(url)
if err != nil {
log.Fatal(err)
}
// In the view:
ui.Video(c, app.trailer).Fill().Fit(ui.Contain)//go:embed waves.gif
var wavesGIF []byte
d, err := ui.NewGIFDecoder(wavesGIF)
if err != nil {
log.Fatal(err)
}
app.waves, err = ui.NewVideoPlayer(d)
if err != nil {
log.Fatal(err)
}
app.waves.SetLoop(true).SetMuted(true)
// In the view:
ui.Video(c, app.waves).Fill().Fit(ui.Cover)The video example plays a GIF behind a window's content and a trailer with controls over it.
Players
Make players once, not in the view, as bitmaps. A new player plays as soon
as a Video shows it. Pause holds it at the frame it shows, and Play
goes on from there, or plays it again from its start once it ended;
Playing tells which, and Ended whether it played to its end, where it
shows its last frame. SetLoop plays it again each time it ends, without
a gap. Seek shows the frame at a time and plays on from there, unless
paused; Position and Duration tell where it plays and how long it is
(0 for a live stream). SetRate plays it faster or slower, its sound at
the same pitch; SetVolume and SetMuted set its sound. Err tells what
stopped it before its end, as a file that does not decode or a stream that
broke off. Close stops it, and its sound. Call a player's methods on the
main thread, in the view or an input handler, and show it in one Video
at a time.
label := "Pause"
if !p.Playing() {
label = "Play"
}
if ui.Button(c.Key("play"), label).Clicked() {
if p.Playing() {
p.Pause()
} else {
p.Play()
}
}
pos := p.Position().Seconds()
if ui.Slider(c.Key("seek"), &pos, 0, p.Duration().Seconds()).Label("Position").Changed() {
p.Seek(time.Duration(pos * float64(time.Second)))
}ui.NewVideoPlayer decodes the first frame at once, which Video shows
from its first frame and which reports a video that does not decode. A
system's video opens at once and loads in the background: Ready is false
and Size 0×0 until its first picture came, and Buffering is true while
a stream waits for data, for the view to show a spinner. Each change of
these builds a frame of the window, so the view never polls.
Playing
A player decodes its frames on a goroutine of its own, a couple ahead of
the one showing, into frames it uses again, and each frame reaches the
screen through a bitmap's Update as it comes due:
the window draws one frame for each of the video's, which After asks
for, and none between. When the decoder falls behind, the window waits
for it without drawing, and of the frames that came due meanwhile shows
only the last, so the video keeps to the clock rather than lag behind it.
A system's video keeps the clock its sound plays by. As each frame of the window builds, the player takes the picture due as the frame shows, where the decoder left it, and the window draws again as the next comes due: a 24 fps video on a 120 Hz display draws 24 frames a second.
A muted video is a picture, as a background is. It moves on only while
the view builds it and some of the window shows: one that went out of the
view, as on another tab, or in a window that is minimized, hidden or
covered by other windows (on macOS, and in browsers), goes on from where it
stood once it shows again, and decodes nothing meanwhile. While the desktop
asks for less motion (Preferences.ReduceMotion), it stands still: a
decoder's at its first frame. A video with sound plays on out of sight, as
a browser tab's does, and plays under less motion too, as the user asked
for it; a decoder's video has no sound, and is always the former kind.
Decoders
ui.NewGIFDecoder decodes animated GIFs, in Go, as browsers show them:
each frame drawn over what the ones before it left, as its disposal says,
and a frame of 10 ms or less shown for 100 ms. The GIF decodes into memory
at once, a byte a pixel for each frame, and each frame is drawn as it
plays. It plays once or loops as SetLoop says, whatever loop count the
GIF gives.
A ui.VideoDecoder of your own plays another format: Next returns the
next frame, a picture of any image type and how long it shows, or io.EOF
after the last, and Rewind goes back to before the first. The player
copies each picture, so a decoder may draw every frame into the same one,
and calls the decoder from one goroutine at a time. A live stream that an
app decodes itself, as with FFmpeg, reaches the screen through a bitmap's
UpdateYUV instead, its frames in Y′CbCr as the
decoder gives them, which the GPU converts.
type frames struct {
pics []*image.RGBA
next int
}
func (f *frames) Next() (ui.VideoFrame, error) {
if f.next == len(f.pics) {
return ui.VideoFrame{}, io.EOF
}
f.next++
return ui.VideoFrame{Image: f.pics[f.next-1], Duration: time.Second / 30}, nil
}
func (f *frames) Rewind() error { f.next = 0; return nil }ui.NewAPNGDecoder decodes animated PNGs the same way, each frame drawn
as its disposal and blending say, and keeps each frame compressed until it
plays. A PNG that is not animated is a video of one frame.
A decoder that also has a method Duration() time.Duration reports the
video's length through Duration; GIFs' and APNGs' do. Seek on a
decoder's video decodes from its start up to the frame.
System video
Package ui/video plays with the system's media framework. Apps that do
not import it link none of it.
| System | Framework | Pictures reach the renderer as |
|---|---|---|
| macOS | AVFoundation (AVPlayer), decoding with VideoToolbox |
CVPixelBuffers in NV12 backed by IOSurfaces, which Metal wraps as textures: no copy |
| Windows | Media Foundation (IMFMediaEngine), decoding with DXVA |
textures in BGRA the engine converts into on its own Direct3D device and shares with the window's: a copy on the GPU |
| Linux | GStreamer 1.x (playbin), decoding with VA-API where installed |
NV12 planes, 1.5 bytes a pixel, which OpenGL uploads as they are and the shader converts |
| Browsers | the video element | the element, which WebGL uploads GPU to GPU |
video.Open opens a file, video.OpenURL a file:, http: or https:
URL or an HLS playlist, and video.OpenData bytes in memory, as an
embedded file, without writing them to a file. video.ErrUnsupported
reports a system without a framework that plays video, as a Linux without
GStreamer and its base plugins or a Windows Server without its Media
Foundation feature, for the app to show a GIF instead. What formats play
is the system's: H.264 and AAC in MP4 play on all of them. Linux needs
gstreamer1.0-plugins-base, and gstreamer1.0-libav (or another H.264
decoder) for H.264; a missing decoder shows as Err.
Windows on Arm's MPEG-4 reader ignores a track's edit list. An H.264
video with B-frames (x264's default) has one that takes back its
reordering delay, so there each picture shows that much late, up to a few
frames behind its sound. Encode a clip without B-frames
(-x264-params bframes=0) where that matters, as the example's trailer
is.
In browsers a video with sound may not play before the user interacted with the page: it then plays muted, as the browser allows.
Accessibility
Videos are decorations that assistive technology does not see, unless
Label names them, as images are.