Class MediaControls
VideoView: play/pause, a scrub bar and a position clock — the
bar every player lays over its picture. Everything here drives the view and only the view
(VideoView.setPaused(boolean), VideoView.seek(long, VideoStreamSource.SeekMode),
VideoView.positionMicros()), so it works with a frozen capture, a source with no
soundtrack, and a player the application built itself.
It reads left to right in either direction, by default. The playback cluster inherits
its arrangement from tape decks and the scrub bar advances the way the tape ran; media playback
is the standing exception in every platform's bidi guidance, so this widget declares LTR
for itself when it is built. That is a declaration and not a law: an application that wants the
controls to follow the tree (some platforms do mirror them) calls
setLayoutDirection(null) to inherit, or passes an explicit direction — the same knob
every widget has (see docs/design/direction-axis.md).
Sound, and which half of it is the toolkit's. A mute button and a volume slider are
built in, because a gain on the soundtrack the player already holds is the toolkit's to turn
(MediaPlayer.audio()). By default they appear only while the media can actually sound
(MediaControls.Sound.AUTO) and step aside otherwise, so a silent clip does not carry a dead volume;
setSound(limn.components.MediaControls.Sound) forces them MediaControls.Sound.ON or MediaControls.Sound.OFF. Muting is gain zero and
never a teardown. Everything behind the gain stays the application's: one that opens
its soundtrack lazily, switches audio tracks or offers subtitles turns the built-ins off and
wires its own widgets into the slots.
The slots. addLeading(limn.scene.Widget<?>) places a widget between the play button and the scrub
bar (where players put their volume), addTrailing(limn.scene.Widget<?>) between the scrub bar and the clock,
and observeRefresh(java.lang.Runnable) gives injected controls a ride on the same heartbeat the built-in ones
update on. setShowPosition(boolean) and setBackdrop(boolean) trim the built-ins.
The bar polls on a timer rather than a ticker, so parked over a paused picture it costs nothing: every write below is guarded, and a frame is only asked for when a number actually moved.
To an assistive technology this is one tool bar holding the transport, and nothing more: the play and mute buttons are real widgets that describe themselves as buttons named by the tooltips this bar keeps in step with their state, the two sliders and the clock describe themselves, and the row and the boxes between them are scaffolding the tree deletes. The bar cannot say anything about a control on the buttons' behalf — its only child is the row — and so it does not try to. A press from a screen reader reaches the same activation a click and a Space release reach.
The tooltips are written where the state they name changes, and not on the heartbeat. The
tree is published before the paint of the same frame, so a tooltip written from the paint
reaches a reader one frame after the change it names; the mute button's name in particular
follows muted, which the setters and the volume slider all write, so each of those
writes the tooltip as it goes.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enumWhether the mute button and volume slider are offered. -
Constructor Summary
ConstructorsConstructorDescriptionMediaControls(VideoView view) A transport forview: play and pause, the scrub bar and the clock, and mute and volume while the media can sound. -
Method Summary
Modifier and TypeMethodDescriptionaddLeading(Widget<?> widget) Adds a widget between the play button and the scrub bar — where players put their volume.addTrailing(Widget<?> widget) Adds a widget between the scrub bar and the position clock.booleanisMuted()Whether the soundtrack is silenced (gain zero; the stream keeps running).observeRefresh(Runnable listener) Observes every refresh of the built-in controls, so a widget injected throughaddLeading(limn.scene.Widget<?>)/addTrailing(limn.scene.Widget<?>)can update on the same heartbeat instead of arming a timer of its own.protected voidDescribes this bar as one horizontal tool bar that controls its view, and nothing else.protected voidonLayout()Containers position children here (measure +Widget.layoutBox(float, float, float, float)per child).protected SizeonMeasure(Constraints constraints) Reports the size this widget wants withinconstraints.protected voidPaints the backdrop and arms the poll.protected floatWhat is left of the theme's focus ring reach past the padding round the row, which is all of it without the backdrop: the play button is then flush with the bar's leading edge, and its ring, and that of any control an application added at either end, hangs past the bar.setBackdrop(boolean paint) Whether the bar paints its own translucent panel (default true).Overrides the ink the built-ins write in — the play glyph and the clock.setMuted(boolean newMuted) Gain zero and never a teardown: a mute that closed the stream would cost a decoder flush every time somebody silenced a video for a moment.setShowPosition(boolean show) Whether the position clock is shown (default true).setSound(MediaControls.Sound mode) Whether the mute button and volume slider are offered (defaultMediaControls.Sound.AUTO).setVolume(float newLevel) Volume in[0, 1], applied as a gain on the soundtrack the player holds; zero also mutes.view()The view these controls drive.floatvolume()The volume unmuting restores, in[0, 1].Methods inherited from class limn.scene.Widget
acceptsTextInput, accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, baselineOffset, baselineOffsetOf, caretRect, children, clipboard, clipHeight, clipsChildren, clipWidth, clipX, clipY, controlSize, cursor, declaredControlSize, declaredLayoutDirection, declaredLocale, focusArrivedBackward, focusArrivedByTraversal, handleUserChange, height, hitTest, hitTestChild, id, imageCursor, inheritanceHost, invalidate, invalidate, invalidateAccessible, isEnabled, isFocusable, isFocused, isRightToLeft, isSelfEnabled, isShowing, isVisible, isVisibleByItsOwnFlag, layoutBox, layoutDirection, locale, localToScene, localToSceneBounds, markNeedsContainedLayout, markNeedsLayout, markNeedsLayoutInPlace, maxHeight, maxWidth, measure, minHeight, minWidth, moveChild, moveChild, moveChild, neutralBase, notifyChange, notifyTextEdit, observeChanges, onAccessibilityAction, onAccessibilityChild, onAccessibilityChildAction, onAccessibilityChildIdentity, onAttached, onCanEdit, onCharTyped, onDetached, onDragExit, onDragOver, onDragStart, onDrop, onEdit, onFocusGained, onFocusLost, onHitTest, onKeyEvent, onMouseEvent, onPaintOverlay, onPreedit, onSyntheticAction, overlayPassesPointer, paintChildren, paintsDecoration, paintsFromBackdrop, paintWidget, parent, remove, remove, requestFocus, requestFocus, revealInView, revealInView, runHandler, scene, sceneNanos, sceneToLocal, self, setAccessibleDescribedBy, setAccessibleDescription, setAccessibleDescription, setAccessibleIgnored, setAccessibleLabelledBy, setAccessibleName, setAccessibleName, setAccessibleRole, setControlSize, setCursor, setEnabled, setFocusable, setId, setImageCursor, setInheritanceHost, setLayoutDirection, setLocale, setMaxHeight, setMaxWidth, setMinHeight, setMinWidth, setPresentedElsewhere, setTooltip, setTooltip, setVisible, shapeText, textRuler, tooltip, tooltipSource, width, x, y
-
Constructor Details
-
MediaControls
A transport forview: play and pause, the scrub bar and the clock, and mute and volume while the media can sound.- Parameters:
view- the view it controls; never null
-
-
Method Details
-
view
The view these controls drive. -
addLeading
Adds a widget between the play button and the scrub bar — where players put their volume. Order of calls is the order on screen. -
addTrailing
Adds a widget between the scrub bar and the position clock. -
setShowPosition
Whether the position clock is shown (default true). -
setBackdrop
Whether the bar paints its own translucent panel (default true). A host that already draws chrome behind the controls — a glass strip over the picture — turns it off. -
observeRefresh
Observes every refresh of the built-in controls, so a widget injected throughaddLeading(limn.scene.Widget<?>)/addTrailing(limn.scene.Widget<?>)can update on the same heartbeat instead of arming a timer of its own. The heartbeat is the poll tick: a paint announces nothing and so refreshes nothing, which makes a tick a truer heartbeat than a paint rate was.- Parameters:
listener- what to run after each refresh; never null- Returns:
- a handle that unregisters; cancelling it twice is a no-op. UI thread
-
setInk
Overrides the ink the built-ins write in — the play glyph and the clock. A bar drawn over someone else's chrome (a glass strip over the picture) sits on a surface the palette knows nothing about, and a theme ink chosen for the theme's own surfaces can land near-black on it.nulls restore the theme's answer. -
setSound
Whether the mute button and volume slider are offered (defaultMediaControls.Sound.AUTO). -
setVolume
Volume in[0, 1], applied as a gain on the soundtrack the player holds; zero also mutes. This is what unmuting restores. -
volume
public float volume()The volume unmuting restores, in[0, 1]. -
setMuted
Gain zero and never a teardown: a mute that closed the stream would cost a decoder flush every time somebody silenced a video for a moment. -
isMuted
public boolean isMuted()Whether the soundtrack is silenced (gain zero; the stream keeps running). -
onAccessibility
Describes this bar as one horizontal tool bar that controls its view, and nothing else.The role is unconditional and does not follow
setBackdrop(boolean): the class paints the backdrop, so a bar that declared nothing would be deleted as scaffolding with a warning naming a toolkit class in an application's log, and the node is wanted with the panel off as much as with it on, because either way this is the bar that holds the transport. The orientation is fixed by the class, which lays its controls in a row and only a row. The relation names the view these controls drive; it is resolved to the view's own node once the view publishes one and is dropped until then.No name is declared, because the bar holds no string that names itself; an application that wants one calls
setAccessibleName. Nothing is declared about the controls: each button, slider and the clock describes itself, and the row and the boxes between them are removed by the tree's own rule.- Overrides:
onAccessibilityin classWidget<MediaControls>- Parameters:
a- the node being described
-
onMeasure
Description copied from class:WidgetReports the size this widget wants withinconstraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.Resolve the
ControlSizeand theLayoutDirectiononce each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, andI18n.locale()answers it wherever text is resolved.- Specified by:
onMeasurein classWidget<MediaControls>
-
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child).- Overrides:
onLayoutin classWidget<MediaControls>
-
paintOutset
protected float paintOutset()What is left of the theme's focus ring reach past the padding round the row, which is all of it without the backdrop: the play button is then flush with the bar's leading edge, and its ring, and that of any control an application added at either end, hangs past the bar. With the backdrop the padding is wider than the ring at every step and nothing is left.- Overrides:
paintOutsetin classWidget<MediaControls>
-
onPaint
Paints the backdrop and arms the poll. It refreshes nothing: a paint may not announce a change, andrefresh()writes widget state that announces -- the bar's value on every advance, the clock's text once a second -- so the refresh runs from the poll tick alone. What that costs is latency, at most one poll interval, and a repaint that no tick follows (a resize, an occlusion redraw) no longer refreshes the transport on its own. What it keeps is the re-arm-on-paint pattern the animations use: the poll stops itself when the bar leaves the screen, and the paint that brings it back is what starts it again.- Overrides:
onPaintin classWidget<MediaControls>
-