MiczFlor / RPi-Jukebox-RFID

A Raspberry Pi jukebox, playing local music, podcasts, web radio and streams triggered by RFID cards, web app or home automation. All plug and play via USB. GPIO scripts available.
http://phoniebox.de
MIT License
1.38k stars 397 forks source link

Resume last played track now working! (especially for audiobooks) #47

Closed Neponator closed 6 years ago

Neponator commented 6 years ago

Hey,

I tried for the last week to implement a set of scripts that allows to resume the last played track and I finally made it work - the Jukebox now saves the last played playlist, song and position and resumes after reboot of the pi. It's not perfect since the song starts for a second before resuming to the last played position. I'm still trying to tweak that with mute/unmute or working with shorter sleep() in milliseconds.

My appraoch (code see below): The VLC which the RPi-Jukebox uses offers a XML file, that shows all information on the playing track (like artist, title, current position, current track number of the playlist). I took those information and saved it into separate text files - (artist and title for a display that I plan for the future) as well as the current track position and playlist number. Since the "download" of this xml is pretty slow (1-3 seconds), I only use it when pressing the button for "pause" or "shutdown". Current track position and playlist number I use in combination with the VLC remote functions "goto x" and "seek x" (seek is already integrated in the code with seek 0 as a possibility to start the track from beginning). "goto x" works similarly, it makes vlc jump to the specific item on the playlist and plays it. To make it work, the playlist cannot be temporary like in the original project, so I changed the path in the rfid_trigger_play.sh to RPi-Jukebox-RFID\shared\playlists\ (First I chose the Folder containing the mp3s itself, however that did not work as all the files in the folder are used to create the playlist, including the m3u's. So I added the subfolder. At the sametime I exported the path of the m3u to a textfile to use after reboot. Now I have all I need, the playlist and the text files containing current position and current track.

I then created a bash-script that runs after boot of the pi, starts the VLC, loads the playlist, jumps to the current track and plays a specific position.

Now to my code:

  1. To make VLC produce the xml change in the rfid_trigger_play.sh the line

cvlc --no-video --network-caching=10000 -I rc --rc-host localhost:4212 "$PLAYLISTPATH" &

to

cvlc --no-video --network-caching=10000 -I rc --extraintf=http --http-password 123 --rc-host localhost:4212 "$PLAYLISTPATH" &

  1. When already in rfid_trigger_play.sh also change the playlist from temporary to permanent:

instead of

PLAYLISTPATH="/tmp/$FOLDERNAME.m3u"

set

PLAYLISTPATH="$PATHDATA/../shared/playlists/$FOLDERNAME.m3u" (not sure if you need to create the folder "playlists" in ./shared first or if the script does it for you - if you want to be sure, create it first with mkdir playlists.

Additionally we want to export the path of the playlist to a textfile, to be able to remember the last used playlist at start of the pi:

Still in rfid_trigger_play.sh add echo "$PATHDATA/../shared/playlists/$FOLDERNAME.m3u" > $PATHDATA/../shared/lastplayed.txt after find "$PATHDATA/../shared/audiofolders/$FOLDERNAME" -type f | sort -n > "$PLAYLISTPATH"

  1. This is my script to exploit the VLC xml,intended to be run when button for "shutdown" or "pause" are pressed (Created with nano /home/pi/RPi-Jukebox-RFID/scripts/getCurrentSongDetails.py)
#!/usr/bin/python3
#/home/pi/RPi-Jukebox-RFID/scripts/getCurrentSongDetails.py
from bs4 import BeautifulSoup
import urllib.request

def get_currentSongDetails():

    password_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
    top_level_url = "http://127.0.0.1:8080/requests/status.xml"
    password_mgr.add_password(None, top_level_url, '', '123')
    handler = urllib.request.HTTPBasicAuthHandler(password_mgr)
    opener = urllib.request.build_opener(handler)
    u = opener.open(top_level_url)
    soup = BeautifulSoup(u, 'html.parser')
    currents = []

    for info in soup.select('info'):            # Selects the info category
        if info['name'] == 'title':             # Selects the title inside the info category
            currents.append(info.text)          # Writes title into the currents-array
    for playid in soup.select('currentplid'):   # Selects the current playlist-number of the track
            currents.append(playid.text)        # Writes the playlist-number in the currents-array
    for current in soup.select('time'):           #Selects the current position in the track
        currents.append(current.text)           # Writes the current position into current-array

    return (currents)
currentSongDetails = get_currentSongDetails()

#with open ("/home/pi/RPi-Jukebox-RFID/shared/latestSong.txt", "w") as text_file:     #for putting on a #display - later!
# for (cur) in currentSongDetails[0]:
#     text_file.write('{}'.format(cur))

with open ("/home/pi/RPi-Jukebox-RFID/shared/latestPlid.txt", "w") as text_file2:
   for (cur) in currentSongDetails[1]:
    text_file2.write('{}'.format(cur))

with open ("/home/pi/RPi-Jukebox-RFID/shared/latesttime.txt", "w") as text_file3:
   for (cur) in currentSongDetails[2]:
    text_file3.write('{}'.format(cur))

Relevant output are the the textfiles latestPlid.txt and latesttime.txt that contain the last played playlist-item and the track position. (I don't know why, but the VLC playlists start with track position 4 - so the first song of the playlist has PLID 4, the second has 5 and so on). Here I'm pretty sure python does not creates the textfiles latestPlid.txt, latesttime.txt and latestsong.txt in ./shared for you - better create them yourself beforehand via nano latestPlid.txt (then CTRL-O and Enter to save, CTRL-X to close; same for latesttime.txt, latestsong.txt).

  1. Now my script that should run while/after booting the pi:
#!/usr/bin/env bash
#/home/pi/RPi-Jukebox-RFID/scripts/lastplayed.sh

sudo pkill vlc #necessary?
VLCPLAYS=$(</home/pi/RPi-Jukebox-RFID/shared/lastplayed.txt)
cvlc --no-video --network-caching=10000 -I rc --extraintf=http --http-password 123 --rc-host localhost:4212 "$VLCPLAYS" &

sleep 5 

PLID=$(</home/pi/RPi-Jukebox-RFID/shared/latestPlid.txt)
echo "goto $PLID" | nc.openbsd -w 1 localhost 4212

sleep 1

SONGTIME=$(</home/pi/RPi-Jukebox-RFID/shared/latesttime.txt)
echo "seek $SONGTIME" | nc.openbsd -w 1 localhost 4212

The service I use to start the script lies in /etc/systemd/system/ and looks like this: (Just copy the code into a new file via nano /etc/systemd/system/resumesong.service then sudo systemctl daemon-reload to inform the system that there's a new service, then sudo systemctl enable resumesong.service to start the service.)

[Unit]
Description=Resume Last Song Service
After=network.target iptables.service firewalld.service rfid-reader.service

[Service]
WorkingDirectory=/home/pi/RPi-Jukebox-RFID
ExecStart=/home/pi/RPi-Jukebox-RFID/scripts/lastplayed.sh
KillMode=process
SendSIGKILL=no

[Install]
WantedBy=multi-user.target
  1. To save playlist, track and position in the track I run the getCurrentSongDetails.py (see No. 2) when play/pause or shutdown buttons are pressed. Theoretically you can run it also when pressing next and/or previous buttons, however knowing my daughter hitting next-button a hundred times to find her favorite song - i really don't know what happens if the "download" of the vlc-xml is requested with the same staccato...

To run the script fetching the data you need to change the gpio-buttons.py in the following way:

add in headline

import time

and change

def def_shutdown():
     check_call("/home/pi/RPi-Jukebox-RFID/scripts/playout_controls.sh -c=shutdown", shell=True)

to

def def_shutdown():
     check_call("/home/pi/RPi-Jukebox-RFID/scripts/getCurrentSongDetails.py")
     time.sleep( 3 )
     check_call("/home/pi/RPi-Jukebox-RFID/scripts/playout_controls.sh -c=shutdown", shell=True)

Also change

def def_halt():
   call("/home/pi/RPi-Jukebox-RFID/scripts/playout_controls.sh -c=playerpause", shell=True)

to

def def_halt():
   call("/home/pi/RPi-Jukebox-RFID/scripts/playout_controls.sh -c=playerpause", shell=True)
   check_call("/home/pi/RPi-Jukebox-RFID/scripts/getCurrentSongDetails.py")

Don't forget to make the new scripts and textfiles accessible with sudo chmod +x <file>!

Done!

I started python only a week ago - so most of my code is stolen or the result of trial and error - if you have any recommendations for simplifying it, let me know.

MiczFlor commented 6 years ago

Hi @Neponator

you started a good feature here, I haven't found the time to really test what you are aiming at. Still, some feedback (to keep your motivation up! We all want your feature!). When I have more time, I will join in the effort.

a) try to give each action in your script more time by adding sleep. Just for testing.

b) the bash rfid_trigger_play.sh creates a new playlist from the folder contents when it is run. Possibly that confuses the script? Timing wise? Try with commenting out the playlist creation?

c) sudo everything and then see if that does the trick... :/

Neponator commented 6 years ago

You're my hero - a) already solved it - it's not only the time between calling the playlist and the track (min sleep 5) but also the time between calling track (goto x) and calling position (seek x) in the track, that requires a sleep 1, otherwise the requests confuse each other. It's running now - many thanks @MiczFlor ! I updated the code in post above. Will keep the issue open to create a manual that is easier to follow :-D

MiczFlor commented 6 years ago

Hi @Neponator Glad it works and yes, please keep it open and a bit of documentation would be great. I am a big fan of pull requests. They write you into the history of the phoniebox code base. And they open another thread to discuss suggestions and changes, see here for an example: https://github.com/MiczFlor/RPi-Jukebox-RFID/pull/42 From your github profile, I can't see how savy you are with git :) You might to read a bit

MiczFlor commented 6 years ago

I am closing this ticket, because of the launch of version 1.0 on master. If you have any issues, please try first if they are still happening on the latest version 1.0. If so, open another ticket, please.

The version 1.0 has not been tested on jessie

Version 1.0 is a major improvement (which will be full of bugs, for sure :) and I want to thank all the contributors and Phoniebox lovers with their input, pictures, bug fixes and suggestions.