Images and Media in Kivy
Learn to integrate images and media in Kivy for mobile apps. Hands-on tutorial covering core concepts and step-by-step implementation.
Focus: integrate images and media in kivy
You've built the logic, wired up the events, and polished your Kivy layouts. But when a button is just a colored rectangle and a profile screen shows only text, the app feels flat. Users expect images, icons, and maybe video or audio that load fast and look sharp on a phone-sized screen. Without a solid plan for media integration, you'll hit blurry textures, frozen UIs on startup, or crashes from unsupported formats. This lesson shows you the reliable way to integrate images and media in Kivy — from loading a simple PNG to playing video — so your app feels production-ready.
The problem this lesson solves
Kivy gives you Image, Video, and SoundLoader widgets, but naive usage creates real pain. Loading a 4K photo directly into an Image widget can stall the UI thread for hundreds of milliseconds. Playing a video with the wrong decoder fails silently. Referencing an asset by a relative path that works on your desktop but breaks on Android is a classic trap. And once you add media, memory becomes a concern: a single full-screen image can use 12 MB of RAM, which matters on a low-end device.
These problems are not exotic. They happen the moment your app progresses beyond "Hello World" and displays a product photo, an avatar, or a tutorial video. Without a systematic approach, you'll lose users to sluggish screens and crash logs. The solution is to integrate images and media deliberately: choose the right widget, load asynchronously, manage memory, and handle formats that are safe across platforms.
Core concept / mental model
Think of Kivy's media widgets as pluggable display panels. An Image widget knows how to load a file and render it, but it doesn't care where the bytes come from. A Video widget plays media through a backend—on desktop it might use GStreamer, on Android a different decoder. Your job is to feed them valid data and let them handle the rest.
The underlying abstraction is the Loader for images. When you assign a source to an Image, Kivy starts a background thread that decodes the image into a texture. This is the magic that keeps your UI responsive: the loading doesn't block your main loop. The Video widget, meanwhile, is a state machine—play, pause, stop—with events you can hook into. SoundLoader follows a similar pattern for audio.
A useful mental model: media is data, widgets are doors. You open the door with a source string, and you close it when you're done to free memory. Always ask three questions: What format is it? (PNG, JPEG, MP4, OGG), Where does it live? (bundled asset, network URL, user file), and How big is it? (dimensions, duration, bitrate). Answering those questions drives every choice you make.
How it works step by step
Step 1: Know your file locations
Kivy apps have two kinds of assets: those you bundle with the app and those you load at runtime. Bundled assets go in an images/ or assets/ folder alongside your .kv file and Python code. At runtime, use os.path.join with the directory of your script to build a reliable path. For mobile, you'll need to declare assets in your build spec (like buildozer.spec for Android).
Step 2: Choose the right widget
Image— for still images: photos, icons, logos. It supports PNG, JPEG, GIF (animated if you setanim_delay).Video— for video playback. It has its own controls if you setstate='play', but you can build custom controls.AsyncImage— a subclass ofImagethat loads from a URL in a background thread. Perfect for profile pictures from a server.SoundLoader— not a widget, but a function that loads audio files (WAV, OGG, MP3 on some platforms).
Step 3: Load efficiently
Assign the source property and let Kivy do the background loading. For repeated use (like icons or textures), consider caching by loading once and reusing the same Image widget or source string. For large images, pre-scale them offline to reduce memory footprint.
Step 4: Handle playback lifecycle
For video, connect to the position and duration events to update a progress bar. Call state='stop' when leaving a screen to release resources. For audio, call stop() and delete the reference when done.
Step 5: Clean up
When you remove a widget from a layout, Kivy eventually garbage-collects it, but you can speed this up by setting source to '' and dropping references. On mobile, this prevents memory leaks from repeatedly loading media.
Hands-on walkthrough
Let's build a small media player screen that shows an image and plays a video with a play/pause button.
Example 1: Loading an image with AsyncImage
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.asyncimage import AsyncImage
from kivy.uix.label import Label
class MediaScreen(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = 'vertical'
# AsyncImage loads in background, good for network URLs
img = AsyncImage(
source='https://picsum.photos/200/300',
size_hint=(1, 0.7)
)
self.add_widget(img)
status = Label(text='Image loaded', size_hint=(1, 0.3))
self.add_widget(status)
class MediaApp(App):
def build(self):
return MediaScreen()
if __name__ == '__main__':
MediaApp().run()
Expected output: A window with an image from the network, loaded without freezing the UI, and a caption below.
Example 2: Playing a video with custom controls
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.video import Video
from kivy.uix.button import Button
class VideoScreen(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = 'vertical'
# Replace with your own video file path
self.video = Video(source='video.mp4', state='stop')
self.video.size_hint = (1, 0.8)
self.add_widget(self.video)
btn = Button(text='Play', size_hint=(1, 0.2))
btn.bind(on_press=self.toggle_play)
self.add_widget(btn)
def toggle_play(self, instance):
if self.video.state == 'play':
self.video.state = 'stop'
instance.text = 'Play'
else:
self.video.state = 'play'
instance.text = 'Pause'
class VideoApp(App):
def build(self):
return VideoScreen()
if __name__ == '__main__':
VideoApp().run()
Expected output: A video player that starts paused; pressing the button toggles play/pause. Replace video.mp4 with a real file in your working directory.
Example 3: Loading a local image safely
import os
from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
class LocalImageScreen(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Build an OS-independent path relative to this file
base_dir = os.path.dirname(__file__)
image_path = os.path.join(base_dir, 'logo.png')
self.add_widget(Image(source=image_path))
class LocalApp(App):
def build(self):
return LocalImageScreen()
if __name__ == '__main__':
LocalApp().run()
Expected output: The logo.png from the same folder is displayed. If the file is missing, you'll get a blank area and a warning in the console.
Pro tip: For mobile, wrap your app directory with
os.path.dirname(__file__)only for development. In production on Android, assets are bundled and accessed viaos.path.join(self.user_data_dir, ...)or using Kivy'sassethandling from the.specfile.
Compare options / when to choose what
| Widget/Technique | Best for | Pros | Cons |
|---|---|---|---|
Image |
Local static files, icons | Fast, simple, supports GIF animation | Loads on main thread if source is local? Actually decodes in background if async is true? |
AsyncImage |
Network images, remote avatars | Non-blocking, easy | Needs internet; no built-in caching |
Video |
MP4, WebM video files | Full playback state machine, events | Requires codec support; can be heavy |
SoundLoader |
Audio files, background music | Simple, works with Kivy's event loop | No visual, needs format support |
| Preloaded texture cache | Repeated images, sprites | Zero re-decoding | You must manage cache yourself |
In practice, use Image for static assets that ship with the app, AsyncImage for any remote content, and Video for anything that moves. For audio, SoundLoader is your friend, but test format compatibility on every target platform.
Troubleshooting & edge cases
- Image not showing — Check the source path. For relative paths, print
os.path.abspath(source)to confirm. On Android, paths are not the same as your development machine; useself.user_data_diror bundle assets properly. - Video doesn't play — Ensure the codec is supported. Kivy uses GStreamer on desktop; on Android, H.264 MP4 is safest. Try a short sample MP4. Also check that the
Videowidget has a size; a zero-size widget won't display. - App freezes on startup with AsyncImage — Even though loading is background, if you have many images, the GPU may still choke. Reduce image dimensions or use a single
Textureand reuse it. - MemoryError or crash on large images — Use smaller images or pre-scale them. On mobile, 2048x2048 textures are common limits.
- SoundLoader returns None — The audio format isn't supported. Use WAV or OGG for reliability across platforms.
- GIF doesn't animate — Set
anim_delayto a small number (e.g., 0.1) to enable animation.
What you learned & what's next
You have now a reliable toolkit to integrate images and media in Kivy. You can pick Image vs AsyncImage, load assets safely, play video with custom controls, and handle common audio pitfalls. You understand the importance of non-blocking loads via AsyncImage and the Loader thread, and you know the cleanup steps to avoid memory leaks.
Key takeaways:
- Kivy's Image widget loads textures on a background thread; use it for local files.
- AsyncImage is ideal for network images to keep the UI responsive.
- Video gives you a state machine and events for building custom players.
- Always verify asset paths and codec support; what works on desktop may not on mobile.
- Release media resources when done to keep memory usage low.
Next step: In the next lesson, you'll learn how to build multi-screen navigation in Kivy, connecting this media player screen to a full app flow with ScreenManager. You'll apply these media assets to create a polished, multi-screen mobile app that feels professional.
Practice recap
Create a small Kivy app that displays a local image and a remote image using Image and AsyncImage, with a button that swaps them. Then add a video that plays under the images, with a Play/Pause button. Test on your desktop, then run it on Android via Buildozer to confirm everything works. Notice how the AsyncImage loads without blocking the window.
Common mistakes
- Using a hard-coded relative path like 'images/logo.png' that works in the IDE but breaks when the app is packaged for mobile.
- Assuming
Videowill play any format; MP4/H.264 is the safest, but other codecs may silently fail on Android. - Loading dozens of large images synchronously, causing UI freezes; always use AsyncImage for remote or heavy files.
- Forgetting to call
stop()on Video or delete SoundLoader references, leading to memory leaks on long sessions. - Expecting
SoundLoaderto load MP3 on all platforms; use WAV or OGG for consistent support.
Variations
- Use
Imagewithkeep_data=Falseto reduce memory if you don't need the raw data, but this disablestextureaccess? Actually keep_data keeps the image data after loading; setting it False saves memory. - Instead of
AsyncImage, manually load textures withCoreImagefor advanced caching and pre-loading. - For video, consider using
VideoPlayerfrom the Kivy Garden for a ready-made player with controls and proxy support.
Real-world use cases
- A social media app showing remote profile pictures and post images loaded asynchronously with AsyncImage to keep the feed scrolling smoothly.
- A cooking app with embedded video tutorials using the Video widget, including custom play/pause controls and progress indicators.
- A music player app using SoundLoader to stream or play local audio files, with a waveform visualization built on Kivy widgets.
Key takeaways
- Kivy's Loader decodes images in a background thread, but you still need AsyncImage for true non-blocking network loads.
- Always build paths with os.path.join and know how assets behave on Android vs desktop via the buildozer.spec.
- Video and SoundLoader depend on codec support; test early with standard formats like MP4 and OGG.
- Reuse textures and pre-scale images to keep memory under control on mobile devices.
- Clean up media widgets when you're done to avoid memory leaks in long-running apps.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.