Skip to content

← Firmware Downloads

Post-install setup · Y2Player · Y2

Y2Player skin engine, version 2

A Y2 custom Software.

Instructions published by the project, shown here word for word.

Y2Player skin engine, version 2

Version 2 extends the declarative renderer for visual theme editors. Set formatVersion to 2 to use the new properties. Neon Grid, Pixel Garden, and Studio Deck are bundled version 2 showcases; version 1 community skins remain supported with their original behavior. Older players reject version 2 with a version error; reload failures retain the Classic fallback. Themes never execute scripts.

The structural contract is skin-v2.schema.json. Semantic validation in SkinParser additionally checks component references, parameters, color names, bindings, focus IDs, file paths, and expansion limits. The schema allows parameter placeholders in templates; the parser validates their resolved values before a skin is published. See example-v2/skin.json for an installable example. It is a development example, not another bundled skin.

Styles and components

styles maps names to shared visual/text properties. A node's style selects one; explicit node properties override it. Supported style properties: color, background, opacity, borderColor, borderWidth, radius, size, bold, font, lineSpacing, overflow, verticalAlign. Styles do not inherit other styles. Palette references continue to use colors.

Components can remain arrays or become objects with parameters and nodes:

{
  "parameters": {"label":"Play", "width":100},
  "nodes": [
    {"type":"text", "bounds":[0,0,"{param.width}",40], "text":"{param.label}"}
  ]
}

Supply props on a component instance to override defaults. Parameters are strings, numbers, or booleans; overrides must have the same scalar type. A whole {param.name} token preserves its type; tokens embedded in strings interpolate. Resolved values still undergo normal action/color/path/binding validation. Defaults are required, unknown parameters are rejected, and each instance is compiled independently. Use parameterized focus IDs when repeating components. A component instance becomes a group; its children use local coordinates. Use anchor: "stretch" or a layout container for content that should resize.

Drawing and text

PropertyValues and behavior
fontPer-element system family or relative font file; omitted inherits the skin font
lineSpacingFont-spacing multiplier, 0.5–3; default 1
verticalAligntop, center (default), bottom
overflowellipsis (default), clip, marquee
marqueeSpeed8–80 logical units/second; default 24
imageFitstretch (legacy default), fit (contain), crop (cover)
imageX, imageYImage alignment from 0 to 1; default 0.5
opacity0–1, including descendants of groups
radiusRounded rectangle or image clipping radius
borderColor, borderWidthIndependent outline, up to 16 units
gradient{ "endColor":"accent", "direction":"vertical" }; horizontal also supported

Version 2 text honors explicit newlines. Wrapping uses font measurements, whole Unicode code points, and word boundaries. Marquee is a single-line, back-and-forth scroll with one-second pauses at the ends. Its clock resets on screen changes; hidden screens do not schedule animation. clip clips overflow without an ellipsis. Fonts load during reload, not during drawing.

New primitives:

Gradients fill primitives/text; they do not tint artwork or images. Borders are rectangular/rounded except for circles. Image fit/crop is clipped to the node bounds. Touch targets use the clipped bounding rectangle.

Layout

Groups and component instances accept layout:

padding is a uniform inset and gap is spacing between cells. In absolute layout, anchor accepts topLeft, topRight, bottomLeft, bottomRight, center, or stretch. Corner offsets are margins from that corner. Center uses x/y as offsets. Stretch uses x/y as symmetric insets; width/height are ignored. Anchors are evaluated against the current parent, including resized row cells. Top-level anchors use the skin viewport. Oversized content clips; containers do not scroll unless they are row repeaters.

Lists and keyboards

rows accepts listMode:

scrolling is page (default), follow (keep selection at the end once past capacity), or center. Carousel always uses center behavior. These are discrete selection-driven windows, not animated free-scrolling. The same cell geometry is used for drawing and hit testing; overlapping cells use last-drawn priority.

keyboard.keyStyle controls gap, radius, color, background, focusColor, focusBackground, borderColor, and borderWidth. Font, bold, and size come from the keyboard node. Key labels and wheel behavior remain owned by SearchKeyboard. Search retains its native keyboard/results navigation.

Visual states and motion

states maps state names to overrides of color, background, opacity, borderColor, borderWidth, radius, size, and bold. Applicable states are applied in this order: normal, paused/playing, active, focused, pressed, disabled. Later properties win. State overrides do not alter action or geometry. disabled: true blocks a node and all descendant controls. Unavailable rows get disabled styling but retain row selection behavior so the player can explain availability. New visibility conditions are pressed, disabled, enabled, and charging.

transitionMs (0–500, default 0) interpolates colors, opacity, radius, and border width between visual states. Text size and weight switch immediately. Motion is limited to 30 redraws/second, stops when the view is hidden/detached, and retains at most one transition per visible node. No general animation timeline or scripts are supported.

Controls, focus, and seeking

Add an entry to top-level navigation to opt a screen into custom wheel focus:

"navigation": {"now_playing":{"initial":"play", "wrap":true}}

Focusable nodes provide an action (or are row containers) and:

"focus": {"id":"play", "group":"transport", "order":1,
          "previous":"previous", "next":"next"}

IDs are unique per screen. Groups sort by name and items by order, preserving source order for ties. Optional previous/next references override wheel order. Disabled, hidden, or fully clipped controls are excluded. nextGroup and previousGroup actions move to the adjacent group. Confirm activates the focused action. A focused row container enters row navigation on Confirm; Back leaves that mode before leaving the screen. Back otherwise keeps the normal reducer behavior, including dismissing messages. Search cannot opt in.

Screens without navigation entries keep all existing wheel behavior. Explicit Now Playing navigation uses the wheel for focus; add volumeUp/volumeDown controls there. Physical transport and volume shortcuts remain available. Navigation under default applies only to screens using the default layout.

Additional actions: volumeUp, volumeDown, shuffle, repeat. They dispatch validated reducer commands and are blocked in Search/FM. Existing actions remain supported. progress.seekable: true seeks on tap release; horizontal progress goes left-to-right, vertical bottom-to-top, and circular clockwise from twelve o'clock. Seek fractions are clamped and ignored without a track or duration, or on Search/FM. No intermediate drag seeks are emitted.

Additional bindings

BindingValue
volumeEffective system or perceptual volume percentage; -- when unavailable
volume.modeVolume mode enum name
shuffle, repeatON/OFF, repeat mode enum name
track.codecTrack metadata codec
track.sampleRate, track.bitDepth, track.channels, track.bitrateMetadata numbers, empty if unknown; Hz, bits, channels, bits/sec
device.charging, device.storageAvailabletrue or false
device.modelReported device model
display.brightnessBrightness percentage

These describe track metadata, not an assertion about the actual DAC output. System volume is cached for one second by the view and refreshed when drawing.

Bounds, compatibility, and checks

Manifest limit remains 256 KiB. JSON nesting is capped at 64 before parsing. Version 1 retains its 512-node source limit. Version 2 compilation has a global 4096-node budget, including validation of unused templates, and each expanded screen remains limited to 4096 nodes including repeated cells. Nested repeaters are rejected even when the outer repeater has capacity one. Source child nesting is capped at 12; component graphs must be acyclic. At most 16 font faces are loaded per skin; external font files retain the 2 MiB limit. Existing image and asset-path limits still apply.

Run from the repository root:

ANDROID_SDK_ROOT=/opt/android-sdk ./gradlew testDebugUnitTest lintDebug assembleRelease
node tools/skin-preview/check.mjs

preview-fixtures.json is consumed by Kotlin tests and the dependency-free browser module. It covers geometry, image fitting, seeking, and text wrapping with a deterministic measurement adapter. Real browser/Android font rasterization is not guaranteed identical: the editor must supply the same fonts and compare device captures for font-metric differences. This module is preview infrastructure, not the web editor UI.

The debug-only SkinPreviewService renders synthetic state on Android and checks navigation/touch without dispatching to playback. Instructions are in the preview tools README.

Published by the schulzcode/Y2Player project [1] [3]