Perl Weekly Challenge 103: What’s playing?

Working from home, you decided that on occasion you wanted some background noise while working. You threw together a network streamer to continuously loop through the files and launched it in a tmux (or screen) session, giving it a directory tree of files to play. During the day, you connected an audio player to the stream, listening through the workday, closing it when done.

For weeks you connect to the stream daily, slowly noticing a gradual drift of the media. After several weeks, you take vacation. When you return, you are pleasantly surprised to find the streamer still running. Before connecting, however, if you consider the puzzle of determining which track is playing.

After looking at a few modules to read info regarding the media, a quick bit of coding gave you a file list. The file list is in a simple CSV format, each line containing two fields: the first the number of milliseconds in length, the latter the media’s title (this example is of several episodes available from the MercuryTheatre.info):

1709363,"Les Miserables Episode 1: The Bishop (broadcast date: 1937-07-23)"
1723781,"Les Miserables Episode 2: Javert (broadcast date: 1937-07-30)"
1723781,"Les Miserables Episode 3: The Trial (broadcast date: 1937-08-06)"
1678356,"Les Miserables Episode 4: Cosette (broadcast date: 1937-08-13)"
1646043,"Les Miserables Episode 5: The Grave (broadcast date: 1937-08-20)"
1714640,"Les Miserables Episode 6: The Barricade (broadcast date: 1937-08-27)"
1714640,"Les Miserables Episode 7: Conclusion (broadcast date: 1937-09-03)"

For this script, you can assume to be provided the following information:

  • the value of $^T ($BASETIME) of the streamer script,
  • the value of time(), and
  • a CSV file containing the media to play consisting of the length in milliseconds and an identifier for the media (title, filename, or other).

Write a program to output which file is currently playing. For purposes of this script, you may assume gapless playback, and format the output as you see fit.

Optional: Also display the current position in the media as a time-like value.

Example

Input: 3 command line parameters: start time, current time, file name

    # starttime
    1606134123

    # currenttime
    1614591276

    # filelist.csv

Output:

    "Les Miserables Episode 1: The Bishop (broadcast date: 1937-07-23)"
    00:10:24

Solution

We have to be careful here. The given start time, and current time are given in seconds, while the duration periods are given in milli seconds. We will therefore multiply the start time and current time (or rather, the difference between them) by 1000, so we can do all the calculations using integers. (We need 64 bit integers — in the given example, the holiday lasted for almost 14 weeks; 5 weeks in milliseconds just fit a 32-bit integer).

We will take input from standard input, assuming lines with three values (a start time, a current time, and a file name) on each line; values separated by white space. (We also assume the file name does not contain white space).

We will read the content of the given array; the track info will be put into an array (both the run time of the track, and its title as separate entries). We will also sum the track lengths — this sum is the time it takes to play the full play list (total_time).

Subtracting the start time from the current time gives us the time passed since we started (time_diff). We’re not interested in the complete loops, so we mod time_diff with total_time, giving us the number of milliseconds passed since the last time the play list was started from the top.

We can now iterate over the array. If the track length is larger than time_diff, this is the track currently playing. We then print the track and current position, and we’re done. Else, we subtract the track length from time_diff, and check the next track.

Perl

Reading the input, and calculating the time difference in milli-seconds:

my ($start_time, $current_time, $file_name) = split;
my $time_diff = ($current_time - $start_time) * 1000;

Reading the file with track info; populating an array with this info, and calculating the time it takes to play all the songs:

    open my $fh, "<", $file_name or die "open '$file_name': $!";
    my @tracks;
    my $total_time = 0;  # Total time to play the entire set of tracks;
                         # in milliseconds.
    while (<$fh>) {
        my ($play_time, $title) = split /\s*,\s*/ => $_, 2;
        push @tracks => [$play_time => $title];
        $total_time += $play_time;
    }

Ignore the playing of the full loops:

$time_diff %= $total_time;

Finding the right track, and printing the results. Note that we print the position in seconds, so we have to divide $time_diff by 1000. We also want integer division in this sections, hence the use of use integer:

    foreach my $track (@tracks) {
        if ($time_diff - $$track [0] < 0) {
            use integer;
            $time_diff    =  $time_diff / 1000;  # Back to seconds.
            my $hours     =  $time_diff / 3600;
            my $minutes   = ($time_diff % 3600) / 60;
            my $seconds   = ($time_diff         % 60);
            say $$track [1],
                sprintf ("%02d:%02d:%02d" => $hours, $minutes, $seconds) =~
                        s/^00://r;  # Remove leading '00:' for track
                                    # shorter than an hour.
            last;
        }
        $time_diff -= $$track [0];
    }

Find the complete program on GitHub.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s