Skillversitas

Images, Video and Audio

Kivy renders media through its widget and core layers: the Image widget for pictures, the Video widget for video with playback controls, and the core.audio providers for sound. The media chapter is about the widgets that play content and the properties that control them - the source, the playback state, the volume, the loop - and the event that tells you when playback ends. This chapter covers images (already met in the core-widgets chapter, here in the media context), the Video widget and its playback model, and audio playback with the provider abstraction.

Images: Source, Stretch, and Loading

Image:
    source: "data/cover.jpg"
    allow_stretch: True
    keep_ratio: True
    size_hint: (1, 0.6)
    # the Image also fires on_source_change when the source loads
    on_source_change: root.on_cover_loaded(self)

The Image widget takes a source - a local path, a URL, or a BytesIO - and renders it, with allow_stretch and keep_ratio controlling how it fits the allocated space (the core-widgets chapter's two properties that make images behave in a layout). The on_source_change event fires when the source is set and when it finishes loading, which is the hook for a "cover loaded" state or for measuring the loaded dimensions (image.texture.size gives the pixel size once the texture is ready). For a gallery or a feed of images, the pattern is the data-driven list with an Image per item, and the lazy-load rule from that chapter applies - set the source when the item scrolls into view, not when it is built, so a long list does not decode every image at once. The Image is also the canvas texture source: a widget's canvas can draw an Image instruction for custom compositing (the canvas-graphics chapter's instruction set).

The Video Widget: Playback as Properties

from kivy.uix.video import Video

class PlayerScreen:
    def __init__(self):
        self.video = Video(source="data/clip.mp4",
                           size_hint=(1, 0.6),
                           state="play")
        # the playback model is a set of properties:
        #   state: "play" | "pause" | "stop"
        #   volume: 0..1
        #   loop: bool
        # and events:
        #   on_state - fires on play/pause/stop
        #   on_finish - fires when playback reaches the end
        self.video.bind(on_finish=self._on_done,
                        on_state=self._on_state)

    def _on_state(self, instance, state):
        # the play/pause button reflects the state
        self.play_btn.text = "Pause" if state == "play" else "Play"

    def _on_done(self, instance):
        # playback finished: loop it, or show the end state
        instance.state = "play" if instance.loop else "stop"

    def toggle(self):
        self.video.state = "pause" if self.video.state == "play" else "play"

The Video widget plays a source with the same property/event model as the rest of Kivy: state is play/pause/stop, volume is 0..1, loop is a bool, and the on_state/on_finish events report the transitions. A player is therefore a set of bindings - the play button toggles state, the state drives the button label, on_finish handles the end - with no imperative playback API to remember. The codec support comes from the video provider (the system's decoder under the hood), so the formats that play depend on the platform's codecs; the common container (MP4/H.264) works everywhere, and an exotic codec that the platform cannot decode will load but not play, which the on_error-style state reports. The pattern generalizes: media playback in Kivy is state as properties, transitions as events, and the UI bound to both.

Audio: The Provider Abstraction

from kivy.core.audio import SoundFactory

# SoundFactory loads a file into a Sound object via the audio provider
# (the platform's audio backend - SDL audio on desktop/mobile)
shot = SoundFactory("data/click.wav")

def play_click():
    if shot.load():          # load() is idempotent; True when ready
        shot.play()

# control properties on the Sound:
#   shot.volume = 0.5
#   shot.stop()
# and the event:
shot.bind(on_finish=lambda *a: print("sound ended"))

Audio goes through core.audio, and SoundFactory(path) is the loader: it asks the platform's audio provider to prepare the file and returns a Sound you play(), stop(), and volume-adjust. The load() call is the readiness check (a file that failed to decode returns False), and the on_finish event fires when the sound ends - the hook for a "next track" in a player. The provider abstraction means the same code plays audio on desktop and mobile through whatever backend the platform offers, which is the Kivy pattern for all the OS-adjacent capabilities (storage, audio, video, the window): a core module defines the interface, the provider implements it per platform, and the app code sees only the interface. For a game or a media app, the Sound objects are preloaded at start (the load is the slow part) and played on the event that triggers them, which keeps the play itself instant.

The Working Rules

  • Images: stretch plus keep-ratio, and lazy-load in a list (set source on show, not on build) so a long gallery does not decode every image at once.
  • Video: playback is state. state/volume/loop are properties, on_state/on_finish are the events, and the player UI is bindings over them - no imperative playback to track.
  • Audio: preload via SoundFactory, check load(), play on the triggering event, and bind on_finish for the end-of-sound action.
  • Formats are a platform capability. The video and audio providers use the system decoders, so the supported formats follow the platform; test the target codecs on the target device, and surface a load failure as a state rather than a crash.
  • Release what you are done with. A Sound you will not replay can be unloaded; a Video that leaves the screen should be stopped, so the decoder and the memory come back.

Summary

Kivy media is the property/event model applied to content: the Image widget renders a source with allow_stretch/keep_ratio (and lazy-loads in a list), the Video widget plays with state/volume/loop as properties and on_state/on_finish as events (a player is bindings over that state), and audio goes through SoundFactory - preload, load() to check readiness, play on the trigger, on_finish for the end action. The provider abstraction (a core interface, a per-platform backend) is what makes the same media code run on desktop and mobile, and the platform's decoders set the format limits - so test the target codecs on the target device and treat a load failure as a state. The unifying rule is the one from the properties chapter: media is state, the UI binds to it, and the events report the transitions.