I've been working heavily on the code for Piamp, and I'm getting closer to a stable enough point where I can go through the code step by step. There are just a few features left to implement before I can get there. In the meantime, here is an updated screenshot of the player:
The most notable difference here is clearly the volume slider, but under the hood I've rewritten just about the whole thing. Here is the current piamp code:
#---------------------------------------------------------------------
# piamp.py
#
# A Python Media Player
#
# By: Josh Archer
#---------------------------------------------------------------------
import os
import pyglet
import Tkinter, tkFileDialog
dir = pyglet.resource.get_settings_path('Piamp')
if not os.path.exists(dir):
os.makedirs(dir)
configFileName = os.path.join(dir, 'settings.cfg')
pyglet.resource.path = ['resources']
pyglet.resource.reindex()
batch = pyglet.graphics.Batch()
class Piamp(pyglet.window.Window):
def __init__(self):
super(Piamp, self).__init__()
self.set_size(800, 480)
#self.set_fullscreen(True)
configFile = open(configFileName, 'wt')
self.load_graphics()
self.canPlay = True
self.canMute = True
self.mainMenuVisible = True
self.frameCount = 0
self.selectedFile = "null"
self.mediaType = 'Audio'
self.currentVolume = 0.5
self.refresh_player()
def load_graphics(self):
#Background:
self.img_background = pyglet.resource.image('background.png')
self.img_background.anchor_x = self.img_background.width/2
self.img_background.anchor_y = self.img_background.height/2
#Main Menu Buttons:
self.img_movies = pyglet.resource.image('movies.png')
self.img_movies.anchor_x = self.img_movies.width/2
self.img_movies.anchor_y = self.img_movies.height/2
self.img_music = pyglet.resource.image('music.png')
self.img_music.anchor_x = self.img_music.width/2
self.img_music.anchor_y = self.img_music.height/2
self.img_shutdown = pyglet.resource.image('shutdown.png')
self.img_shutdown.anchor_x = self.img_shutdown.width/2
self.img_shutdown.anchor_y = self.img_shutdown.height/2
self.img_reboot = pyglet.resource.image('reboot.png')
self.img_reboot.anchor_x = self.img_reboot.width/2
self.img_reboot.anchor_y = self.img_reboot.height/2
self.img_eq = pyglet.resource.image('equalizer.png')
self.img_eq.anchor_x = self.img_eq.width/2
self.img_eq.anchor_y = self.img_eq.height/2
#Player Control Buttons:
self.img_mute = pyglet.resource.image('mute.png')
self.img_mute.anchor_x = self.img_mute.width/2
self.img_mute.anchor_y = self.img_mute.height/2
self.img_unmute = pyglet.resource.image('unmute.png')
self.img_unmute.anchor_x = self.img_unmute.width/2
self.img_unmute.anchor_y = self.img_unmute.height/2
self.img_prev = pyglet.resource.image('previous.png')
self.img_prev.anchor_x = self.img_prev.width/2
self.img_prev.anchor_y = self.img_prev.height/2
self.img_rewind = pyglet.resource.image('rewind.png')
self.img_rewind.anchor_x = self.img_rewind.width/2
self.img_rewind.anchor_y = self.img_rewind.height/2
self.img_play = pyglet.resource.image('play.png')
self.img_play.anchor_x = self.img_play.width/2
self.img_play.anchor_y = self.img_play.height/2
self.img_pause = pyglet.resource.image('pause.png')
self.img_pause.anchor_x = self.img_pause.width/2
self.img_pause.anchor_y = self.img_pause.height/2
self.img_ff = pyglet.resource.image('ff.png')
self.img_ff.anchor_x = self.img_ff.width/2
self.img_ff.anchor_y = self.img_ff.height/2
self.img_next = pyglet.resource.image('next.png')
self.img_next.anchor_x = self.img_next.width/2
self.img_next.anchor_y = self.img_next.height/2
#Volume Control Graphics
self.img_volume = pyglet.resource.image('volume.png')
self.img_volume.anchor_y = self.img_volume.height/2
self.img_volume_fill = pyglet.resource.image('volume_fill.png')
#self.img_volume_fill.anchor_y = self.img_volume_fill.height/2
#self.maxFill = self.img_volume_fill.width
def update_sprites(self):
#print("Entering update_sprites function...")
self.backgroundSprite = pyglet.sprite.Sprite(self.img_background, x=400, y=240, batch=batch)
if(self.mainMenuVisible == True):
self.mainMenuSprites = [pyglet.sprite.Sprite(self.img_movies, x=280, y=315, batch=batch),
pyglet.sprite.Sprite(self.img_music, x=525, y=315, batch=batch),
pyglet.sprite.Sprite(self.img_shutdown, x=60, y=425, batch=batch),
pyglet.sprite.Sprite(self.img_reboot, x=60, y=305, batch=batch),
pyglet.sprite.Sprite(self.img_eq, x=60, y=185, batch=batch)]
if(self.canPlay == True and self.canMute == True):
self.controlSprites = [pyglet.sprite.Sprite(self.img_mute, x=50, y=108, batch=batch),
pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_play, x=242, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
if(self.canPlay == False and self.canMute == True):
self.controlSprites = [pyglet.sprite.Sprite(self.img_mute, x=50, y=108, batch=batch),
pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_pause, x=242, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
if(self.canPlay == False and self.canMute == False):
self.controlSprites = [pyglet.sprite.Sprite(self.img_unmute, x=50, y=108, batch=batch),
pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_pause, x=242, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
if(self.canPlay == True and self.canMute == False):
self.controlSprites = [pyglet.sprite.Sprite(self.img_unmute, x=50, y=108, batch=batch),
pyglet.sprite.Sprite(self.img_prev, x=50, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_rewind, x=146, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_play, x=242, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_ff, x=338, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_next, x=434, y=35, batch=batch)]
self.volumeSprites = [pyglet.sprite.Sprite(self.img_volume, x=498, y=35, batch=batch),
pyglet.sprite.Sprite(self.currentFill, x=498, y=3, batch=batch)]
def update_volume_fill(self):
self.currentFill = self.img_volume_fill.get_region(x=0, y=0, width=int(self.img_volume_fill.width * self.currentVolume), height=64)
def update_volume(self):
self.player.volume = self.currentVolume
def on_draw(self):
self.clear()
if(self.frameCount%10 == 0):
#print("Resetting frame count")
self.frameCount = 0
self.update_volume_fill()
self.update_volume()
self.update_sprites()
batch.draw()
self.frameCount += 1
def open_file(self):
self.root = Tkinter.Tk()
self.root.withdraw()
if(self.mediaType == 'Audio'):
f = tkFileDialog.askopenfilename(parent = self.root, title = 'Please select an audio file:', filetypes = [('MP3 Files', '.mp3'), ('WAV Files', '.wav'), ('All Files', '.*')], initialdir = 'F:\\Media\\Music\\')
if(f):
file = f
self.root.destroy()
return file
else:
self.root.destroy()
return None
else:
f = tkFileDialog.askopenfilename(parent = self.root, title = 'Please select a video file:', filetypes = [('AVI Files', '.avi'), ('MPEG Files', '.mpeg'), ('MKV Files', '.mkv'), ('All Files', '.*')], initialdir = 'F:\\Media\\Video\\')
if(f):
file = f
self.root.destroy()
return file
else:
self.root.destroy()
return None
def refresh_player(self):
self.player = pyglet.media.Player()
@self.player.event('on_eos')
def auto_next():
self.next()
self.player.volume = self.currentVolume
def previous(self):
#Go back to the previous file in the current directory
self.player.canPlay = True
self.player.pause()
if(self.player.source != 'null'):
#Find the index of the first / from the end of our source filepath
sourceIndex = self.selectedFile.rfind('/')
#Now we can get the name of the directory (including path)
directory = self.selectedFile[:sourceIndex + 1]
previousFile = 'null'
if(self.mediaType == 'Audio'):
for file in os.listdir(directory):
if file.endswith('.MP3') or file.endswith('.mp3'):
print('Previous Button: Checking ' + file)
if(self.selectedFile == (directory + file)):
print('Previous Button: Match!')
#Found the current file, now we need to check if there is a file before this in the directory, and play that file if there is
if(previousFile != 'null'):
print('Previous Button: Previous file is ' + previousFile)
self.refresh_player()
media = pyglet.media.load(directory + previousFile)
self.player.queue(media)
self.selectedFile = directory + previousFile
self.player.play()
self.player.canPlay = False
else:
print('Previous Button: Current file first in directory!')
return
else:
#Assign the current file to previousFile so that we can refer back to it if the next file is the current source
previousFile = file
if file.endswith('.WAV') or file.endswith('.wav') or file.endswith('.Wav') or file.endswith('.WAv') or file.endswith('.waV') or file.endswith('.wAV'):
print('Previous Button: Checking ' + file)
if(self.selectedFile == (directory + file)):
print('Previous Button: Match!')
#Found the current file, now we need to check if there is a file before this in the directory, and play that file if there is
if(previousFile != 'null'):
print('Previous Button: Previous file is ' + previousFile)
self.refresh_player()
media = pyglet.media.load(directory + previousFile)
self.player.queue(media)
self.selectedFile = directory + previousFile
self.player.play()
self.player.canPlay = False
else:
print('Previous Button: Current file first in directory!')
return
else:
#Assign the current file to previousFile so that we can refer back to it if the next file is the current source
previousFile = file
else:
print('Previous Button: Source is null!')
def next(self):
#Go to the next file in the current directory
self.player.canPlay = True
self.player.pause()
if(self.player.source != 'null'):
#Find the index of the first / from the end of our source filepath
sourceIndex = self.selectedFile.rfind('/')
#Now we can get the name of the directory (including path)
directory = self.selectedFile[:sourceIndex + 1]
foundCurrent = False
isLastFile = True
if(self.mediaType == 'Audio'):
for file in os.listdir(directory):
if file.endswith('.MP3') or file.endswith('.mp3') or file.endswith('.mP3') or file.endswith('.Mp3'):
if(foundCurrent):
print('Next Button: Next file is ' + file)
self.refresh_player()
media = pyglet.media.load(directory + file)
self.player.queue(media)
self.selectedFile = (directory + file)
self.player.play()
self.player.canPlay = False
#Use this to tell program we successfully loaded the next file
isLastFile = False
return
if(self.selectedFile == (directory + file)):
#Found the current file, so change foundCurrent to True
foundCurrent = True
if file.endswith('.WAV') or file.endswith('.wav') or file.endswith('.Wav') or file.endswith('.WAv') or file.endswith('.waV') or file.endswith('.wAV'):
if(foundCurrent):
print('Next Button: Next file is ' + file)
self.refresh_player()
media = pyglet.media.load(directory + file)
self.player.queue(media)
self.selectedFile = (directory + file)
self.player.play()
self.player.canPlay = False
#Use this to tell program we successfully loaded the next file
isLastFile = False
return
if(self.selectedFile == (directory + file)):
#Found the current file, so change foundCurrent to True
foundCurrent = True
if(isLastFile):
print('Next Button: Current file is last in the directory!')
def check_for_button(self, x, y):
#Check all button coordinates against x and y, return True if a button was hit or False if not
if( (525 - (self.img_music.width / 2)) < x < (525 + (self.img_music.width / 2)) and ( (315 - (self.img_music.height / 2)) < y < (315 + (self.img_music.height / 2)) ) ):
#coordinate lies inside the music button
if(self.player.playing):
self.player.canPlay = True
self.player.pause()
wasPlaying = True
else:
wasPlaying = False
self.mediaType = 'Audio'
filename = self.open_file()
if(filename):
self.selectedFile = filename
print('Opening ' + self.selectedFile + ' ...')
#Drop the current queue so we can build a new one by overwriting our player with a fresh one
self.refresh_player()
media = pyglet.media.load(filename)
self.player.queue(media)
#Start playback from the selected file
self.canPlay = False
self.player.play()
else:
print('Dialog cancelled')
if(wasPlaying):
self.player.play()
if( (280 - (self.img_movies.width / 2)) < x < (280 + (self.img_movies.width / 2)) and ( (315 - (self.img_movies.height /2)) < y < (315 + (self.img_movies.height /2)) ) ):
#coordinate lies inside the movies button
if(self.player.playing):
self.player.canPlay = True
self.player.pause()
wasPlaying = True
else:
wasPlaying = False
self.mediaType = 'Video'
filename = self.open_file()
if(filename):
self.selectedFile = filename
print('Opening ' + self.selectedFile + ' ...')
#Drop the current queue so we can build a new one by overwriting our player with a fresh one
self.refresh_player()
media = pyglet.media.load(filename)
self.player.queue(media)
#Start playback from the selected file
self.canPlay = False
self.player.play()
else:
print('Dialog cancelled')
if(wasPlaying):
self.player.play()
if( (242 - (self.img_play.width / 2)) < x < (242 + (self.img_play.width / 2)) and ( (35 - (self.img_play.height / 2)) < y < (35 + (self.img_play.height / 2)) ) ):
#coordinate lies inside the play/pause button
if(self.canPlay):
print('Play Button: Playing ' + self.selectedFile)
self.player.play()
self.canPlay = False
else:
print('Pause Button: Pausing ' + self.selectedFile)
self.player.pause()
self.canPlay = True
if( (434 - (self.img_next.width / 2)) < x < (434 + (self.img_next.width / 2)) and ( (35 - (self.img_next.height / 2)) < y < (35 + (self.img_next.height / 2)) ) ):
#coordinate lies inside the next button
print('Clicked next button...')
self.next()
if( (50 - (self.img_prev.width / 2)) < x < (50 + (self.img_prev.width / 2)) and ( (35 - (self.img_prev.height / 2)) < y < (35 + (self.img_prev.height / 2)) ) ):
#coordinate lies inside the previous button
print('Clicked previous button...')
self.previous()
if( (50 - (self.img_mute.width / 2)) < x < (50 + (self.img_mute.width / 2)) and ( (108 - (self.img_mute.height / 2)) < y < (108 + (self.img_mute.height / 2)) ) ):
#coordinate lies inside the mute/unmute button
if(self.canMute):
print('Mute Button: Audio Muted!')
self.player.volume = 0
self.canMute = False
else:
print('Mute Button: Audio Unmuted!')
self.player.volume = self.currentVolume
self.canMute = True
if( (146 - (self.img_rewind.width / 2)) < x < (146 + (self.img_rewind.width / 2)) and ( (35 - (self.img_rewind.height / 2)) < y < (35 + (self.img_rewind.height / 2)) ) ):
#coordinate lies inside the rewind button
if(self.player.playing or self.canPlay):
print('Rewind Button: Seeking backwards 10 seconds.')
newPosition = self.player.time - 10.0
if(newPosition < 0.0):
print('Rewind Button: New time is negative, set to 0 instead.')
newPosition = 0.0
self.player.seek(newPosition)
if(self.canPlay):
pass
else:
self.player.play()
if( (338 - (self.img_ff.width / 2)) < x < (338 + (self.img_ff.width / 2)) and ( (35 - (self.img_ff.height / 2)) < y < (35 + (self.img_ff.height / 2)) ) ):
#coordinate lies inside the fast forward button
if(self.player.playing or self.canPlay):
print('FF Button: Seeking forwards 10 seconds.')
newPosition = self.player.time + 10.0
#Note: We don't need to check if the time is past the end of the track because the Player() class will automatically clamp this to the end of the source
self.player.seek(newPosition)
if(self.canPlay):
pass
else:
self.player.play()
self.volumeSprites = [pyglet.sprite.Sprite(self.img_volume, x=498, y=35, batch=batch),
pyglet.sprite.Sprite(self.img_volume_fill, x=498, y=35, batch=batch)]
if( (498 < x < (498 + self.img_volume.width)) and ( (35 - (self.img_volume.height / 2)) < y < (35 + (self.img_volume.height / 2)) ) ):
#coordinate lies inside the volume control
#First, we need to convert the image's width to a percentage scale
ratio = 100.0 / self.img_volume.width / 100.0
#To make more sense of this, here is an example:
#100 / 256 = 0.390625
#If our touch/mouse location was 128 (right in the middle of the image), we want to set our volume to 50%, or 0.5
#Since 128 * 0.390625 = 50, we still need to divide that by 100 to get the percentage (or 0 to 1 decimal) that we want to use.
#Therefore, instead of 100 / 256 = 0.390625, we need a ratio of 100 / 256 / 100 = 0.00390625
#Now we need to find our x position in relation to the image, instead of the screen like it currently is
local_x = x - 498
#Let's use our new conversion factor to set the volume appropriately
self.currentVolume = local_x * ratio
def on_mouse_press(self, x, y, button, modifiers):
self.check_for_button(x, y)
if __name__ == '__main__':
window = Piamp()
#window.set_mouse_visible(False)
pyglet.app.run()
I've decided to utilize Tkinter and tkFileDialog for the file opening window, which looks like this:
This dialog will look different depending on the operating system that runs it. Tkinter works on a variety of operating systems, so it was an easy choice for this functionality.
To give you an idea on the state of the player:
Working:
- Volume Slider
- Previous Button
- Rewind
- Play/Pause
- Fast Forward
- Next Button
- Mute/Unmute
- Music Button (Open audio file)
- Movies Button (Open video file)
Not Yet Implemented:
- Show current selected file in text box
- Shutdown button
- Reboot button
- Equalizer button
- Video Playback - Currently player only plays audio for video files
In case you want to play around with the player (or the code) yourself, here is a zip file of the entire project:
Stay tuned - I'll be finishing up as much as possible tomorrow, and then I'm transferring all of this to the Pi and getting it installed in the car!