- Explain what a callback is in plain English
- Use
on_song_selectedto respond when the user taps a track - Use
play(),pause(), andresume()to control playback - Use
on_track_endedto move to the next song automatically
Part 1 β What is a callback?
Up until now every piece of code you have written has run immediately from top to bottom. But a music player needs to wait β wait for the user to tap a song, wait for the song to end, wait for the pause button to be pressed.
A callback is a function that you write, but that you hand to someone else to call later, when something specific happens.
Think of it like leaving a note for a delivery driver: "When the parcel arrives, ring the doorbell." You write the instruction, but the driver decides when to carry it out. Your function is the instruction; the device is the delivery driver.
Here is the simplest possible example to show the idea:
def say_hello():
print("Hello!")
# We pass say_hello to something else β note: NO brackets after the name!
# We are handing over the function itself, not calling it right now.
on_song_selected(say_hello)
say_hello() β with brackets β calls the function right now.
say_hello β without brackets β refers to the function as a value
that can be passed around. When you register a callback you always use the
no-brackets form.
Part 2 β Responding when a song is tapped
on_song_selected registers a function that will be called whenever
the user taps a song in the list. Your function receives the Track
object for the song they chose:
from pypod import on_song_selected, play
def handle_selection(track):
play(track)
on_song_selected(handle_selection)
There is a neater way to write exactly the same thing, using the decorator
syntax. Place @on_song_selected on the line directly above your function
definition, and Python registers it for you automatically:
@on_song_selected
def handle_selection(track):
play(track)
@ mean?
The @ symbol is just a shortcut. @on_song_selected above a
function is exactly the same as writing
on_song_selected(handle_selection) below it.
Both styles work; the @ style just keeps things tidy.
Part 3 β The play, pause, and resume functions
Once a track is playing, you will want to be able to pause and resume it. The play/pause button on the Now Playing screen calls these functions automatically β but you can also call them yourself:
from pypod import play, pause, resume, is_playing
# Start playing a track
play(my_track)
# Pause (only does something if a track is playing)
pause()
# Resume (only does something if a track is paused)
resume()
# Check whether something is currently playing
if is_playing():
print("Music is playing!")
Part 4 β Moving to the next song automatically
on_track_ended works in the same way as on_song_selected,
except it fires when the current track finishes playing. Register a function there
to move to the next track automatically:
from pypod import on_track_ended, next_track
@on_track_ended
def handle_end():
next_track() # play the next song in the list
Part 5 β Keeping track of the current song
In later lessons you will need to know which track is playing (to look up its lyrics, for example). Store it in a variable each time a new song starts:
current_track = None # no song selected yet
@on_song_selected
def handle_selection(track):
global current_track # we are updating a variable outside this function
current_track = track
play(track)
global do?
Normally, any variable you create or change inside a function belongs only to that
function and disappears when the function ends. Writing global current_track
tells Python: "I mean the module-level variable, not a local copy". Without it,
current_track would only update inside the function, and the rest of
your code would never see the change.
Part 6 β Putting it all together
main.pyfrom pypod import (
Track, add_track,
play, next_track,
on_song_selected, on_track_ended,
list_music_files, music_path, lyrics_path,
start,
)
# --- functions from previous lessons ---
def parse_filename(filename): ...
def load_lyrics(path): ...
# --- build the track list ---
for filename in list_music_files():
title, artist = parse_filename(filename)
track = Track(title=title, artist=artist,
file_path=music_path(filename),
lyrics=load_lyrics(lyrics_path(title)))
add_track(track)
# --- callbacks ---
current_track = None
@on_song_selected
def handle_selection(track):
global current_track
current_track = track
play(track)
@on_track_ended
def handle_end():
next_track()
start()
- Add the
current_trackvariable and the two callback functions to yourmain.py. - Run the code, tap a song β it should start playing and switch to the Now Playing screen.
- Let the song finish β it should move to the next one automatically.
- Check that the play/pause button and the skip buttons on the Now Playing page all work.
-
Print the title of the current track to the console every time a new song starts.
(Hint: add a
printinsidehandle_selection.) -
The
on_track_endedcallback currently moves to the next track. Can you make it loop back to the very first track when the last song ends? (Hint: you may want to store the full track list in a variable and check the index.)