|
|
@@ -2,17 +2,22 @@
|
|
|
use strict;
|
|
|
use warnings;
|
|
|
use POSIX;
|
|
|
-use File::Touch;
|
|
|
use File::Basename;
|
|
|
-use Time::HiRes qw( usleep sleep time );
|
|
|
use IPC::System::Simple;
|
|
|
+use Time::HiRes qw( usleep sleep time );
|
|
|
|
|
|
# Package name
|
|
|
package Audio;
|
|
|
|
|
|
# Config
|
|
|
my $MEDIA_PATH = `~/bin/video/mediaPath`;
|
|
|
-my %FILES = ();
|
|
|
+
|
|
|
+# App config
|
|
|
+my $DATA_DIR = DMX::dataDir();
|
|
|
+my $QT_FILE = $DATA_DIR . 'QT_PLAYER';
|
|
|
+my $QT_SOCK = $QT_FILE . '.socket';
|
|
|
+my $WAIT_DELAY = 0.1;
|
|
|
+my $MAX_WAIT = 5 / $WAIT_DELAY;
|
|
|
|
|
|
# Debug
|
|
|
my $DEBUG = 0;
|
|
|
@@ -20,131 +25,82 @@ if ($ENV{'DEBUG'}) {
|
|
|
$DEBUG = 1;
|
|
|
}
|
|
|
|
|
|
-# Execute the provided string with osascript
|
|
|
-sub runApplescript($) {
|
|
|
- my ($script) = @_;
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR 'AppleScript: ' . $script . "\n";
|
|
|
- }
|
|
|
-
|
|
|
- my $retval = IPC::System::Simple::capture('osascript', '-e', $script);
|
|
|
- $retval =~ s/[\r\n]$//;
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR "\tAppleScript result: " . $retval . "\n";
|
|
|
- }
|
|
|
+# State
|
|
|
+my %FILES = ();
|
|
|
+my $SOCK = undef();
|
|
|
|
|
|
- return $retval;
|
|
|
-}
|
|
|
+# Send the provided arguments to the QT socket
|
|
|
+sub sendCmd(@) {
|
|
|
|
|
|
-# Set or get the system output volume
|
|
|
-sub systemVolume($) {
|
|
|
- my ($new) = @_;
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR "Audio::systemVolume()\n";
|
|
|
+ # Connect the socket as needed
|
|
|
+ if (!$SOCK) {
|
|
|
+ $SOCK = DMX::clientSock($QT_SOCK);
|
|
|
}
|
|
|
|
|
|
- if ($new) {
|
|
|
- runApplescript('set volume output volume ' . $new);
|
|
|
- } else {
|
|
|
- return runApplescript('get output volume of (get volume settings)');
|
|
|
+ # Ensure the data is compatible with the encoding scheme
|
|
|
+ foreach my $part (@_) {
|
|
|
+ if ($part =~ /\|/) {
|
|
|
+ die('Invalid QT command component: ' . $part . "\n");
|
|
|
+ }
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-# Set or get a document's volume
|
|
|
-sub volume($$) {
|
|
|
- my ($name, $new) = @_;
|
|
|
+ # Build the command stirng
|
|
|
+ my $str = join('|', @_);
|
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Audio::volume(): ' . $name . "\n";
|
|
|
- }
|
|
|
- if (!loaded($name)) {
|
|
|
- die('Invalid QT document: ' . $name . "\n");
|
|
|
+ print STDERR 'Sending QT_PLAYER command: ' . $str . "\n";
|
|
|
}
|
|
|
|
|
|
- if ($new) {
|
|
|
- runApplescript('tell application "QuickTime Player" to set audio volume of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
|
|
|
- } else {
|
|
|
- return runApplescript('tell application "QuickTime Player" to get audio volume of document ' . $FILES{$name}->{'name'});
|
|
|
- }
|
|
|
+ # Send the command
|
|
|
+ $SOCK->send($str)
|
|
|
+ or die("Unable to send to QT socket\n");
|
|
|
}
|
|
|
|
|
|
-# Add an load a document
|
|
|
-sub addLoad($$) {
|
|
|
- my ($name, $path) = @_;
|
|
|
+# Execute the provided string with osascript
|
|
|
+sub runApplescript($) {
|
|
|
+ my ($script) = @_;
|
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Audio::addLoad(): ' . $name . "\n";
|
|
|
+ print STDERR 'AppleScript: ' . $script . "\n";
|
|
|
}
|
|
|
- add($name, $path);
|
|
|
- load($name);
|
|
|
-}
|
|
|
|
|
|
-# Add a document to the available list
|
|
|
-sub add($$) {
|
|
|
- my ($name, $path) = @_;
|
|
|
+ my $retval = IPC::System::Simple::capture('osascript', '-e', $script);
|
|
|
+ $retval =~ s/[\r\n]$//;
|
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Audio::add(): ' . $name . ', ' . $path . "\n";
|
|
|
- }
|
|
|
-
|
|
|
- # Construct an absolute file path
|
|
|
- if (!($path =~ /^\//)) {
|
|
|
- $path = $MEDIA_PATH . '/' . $path;
|
|
|
- }
|
|
|
-
|
|
|
- # Validate
|
|
|
- if (!-r $path) {
|
|
|
- die('Invalid audio path: ' . $path . "\n");
|
|
|
+ print STDERR "\tAppleScript result: " . $retval . "\n";
|
|
|
}
|
|
|
|
|
|
- # We only support one document per name
|
|
|
- drop($name);
|
|
|
-
|
|
|
- # Append the array
|
|
|
- my %tmp = ('path' => $path);
|
|
|
- $FILES{$name} = \%tmp;
|
|
|
+ return $retval;
|
|
|
}
|
|
|
|
|
|
-# Drop a document from the available list, unloaded as necessary
|
|
|
-sub drop($) {
|
|
|
- my ($name) = @_;
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR 'Audio::drop(): ' . $name . "\n";
|
|
|
- }
|
|
|
-
|
|
|
- # Stop and unload as necessary
|
|
|
- if (loaded($name)) {
|
|
|
- stop($name);
|
|
|
- unload($name);
|
|
|
- }
|
|
|
+# Load a document
|
|
|
+sub load($$) {
|
|
|
+ my ($name, $path) = @_;
|
|
|
+ sendCmd('LOAD', $name, $path);
|
|
|
|
|
|
- # Drop as necessary
|
|
|
- if (available($name)) {
|
|
|
- delete($FILES{$name});
|
|
|
+ # Wait for the file to load
|
|
|
+ my $count = 0;
|
|
|
+ while (!loaded($name)) {
|
|
|
+ sleep($WAIT_DELAY);
|
|
|
+ $count++;
|
|
|
+ if ($count > $MAX_WAIT) {
|
|
|
+ die('Unable to open file: ' . $name . "\n");
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
# Is the named document loaded?
|
|
|
sub loaded($) {
|
|
|
my ($name) = @_;
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR 'Audio::loaded(): ' . $name . "\n";
|
|
|
- }
|
|
|
-
|
|
|
- if (available($name) && defined($FILES{$name}->{'name'})) {
|
|
|
- return 1;
|
|
|
- }
|
|
|
- return 0;
|
|
|
+ parseList();
|
|
|
+ return exists($FILES{$name});
|
|
|
}
|
|
|
|
|
|
-# Is the named document configured?
|
|
|
-sub available($) {
|
|
|
+# Unload the named document
|
|
|
+sub unload($) {
|
|
|
my ($name) = @_;
|
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Audio::available(): ' . $name . "\n";
|
|
|
- }
|
|
|
-
|
|
|
- if (defined($name) && defined($FILES{$name})) {
|
|
|
- return 1;
|
|
|
+ print STDERR 'Audio::loadAudio(): ' . $name . "\n";
|
|
|
}
|
|
|
- return 0;
|
|
|
+ sendCmd('UNLOAD', $name);
|
|
|
}
|
|
|
|
|
|
# Play the named document, waiting for completion
|
|
|
@@ -154,12 +110,12 @@ sub play($) {
|
|
|
print STDERR 'Audio::play(): ' . $name . "\n";
|
|
|
}
|
|
|
|
|
|
- # Start playback
|
|
|
- background($name);
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
|
|
|
- # Wait for it to complete
|
|
|
my @cmd = ('tell application "QuickTime Player"');
|
|
|
- push(@cmd, 'repeat while playing of document ' . $FILES{$name}->{'name'} . ' = true');
|
|
|
+ push(@cmd, 'play document ' . $FILES{$name});
|
|
|
+ push(@cmd, 'repeat while playing of document ' . $FILES{$name} . ' = true');
|
|
|
push(@cmd, 'delay 0.1');
|
|
|
push(@cmd, 'end repeat');
|
|
|
push(@cmd, 'end tell');
|
|
|
@@ -172,12 +128,39 @@ sub background($) {
|
|
|
if ($DEBUG) {
|
|
|
print STDERR 'Audio::background(): ' . $name . "\n";
|
|
|
}
|
|
|
- if (!loaded($name)) {
|
|
|
- die('Invalid QT document: ' . $name . "\n");
|
|
|
+
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
+
|
|
|
+ runApplescript('tell application "QuickTime Player" to play document ' . $FILES{$name});
|
|
|
+}
|
|
|
+
|
|
|
+# Pause the named document
|
|
|
+sub pause($) {
|
|
|
+ my ($name) = @_;
|
|
|
+ if ($DEBUG) {
|
|
|
+ print STDERR 'Audio::pause(): ' . $name . "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
+
|
|
|
+ runApplescript('tell application "QuickTime Player" to pause document ' . $FILES{$name});
|
|
|
+}
|
|
|
+
|
|
|
+# Stop playback of the named document
|
|
|
+sub stop($) {
|
|
|
+ my ($name) = @_;
|
|
|
+ if ($DEBUG) {
|
|
|
+ print STDERR "Audio::stopAudio()\n";
|
|
|
}
|
|
|
|
|
|
- # Start playback
|
|
|
- runApplescript('tell application "QuickTime Player" to play document ' . $FILES{$name}->{'name'});
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
+
|
|
|
+ # Stop and rewind
|
|
|
+ runApplescript('tell application "QuickTime Player" to stop document ' . $FILES{$name});
|
|
|
+ runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name} . ' to 0');
|
|
|
}
|
|
|
|
|
|
# Is the named document playing?
|
|
|
@@ -187,8 +170,10 @@ sub playing($) {
|
|
|
print STDERR 'Audio::playing(): ' . $name . "\n";
|
|
|
}
|
|
|
|
|
|
- # Ask QT if the document is playing
|
|
|
- # Note that while QT has a "playing" property it is only true when rate == 1.0
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
+
|
|
|
+ # QT has a "playing" property but it is only true when rate == 1.0
|
|
|
my $rate = rate($name);
|
|
|
if ($rate > 0) {
|
|
|
return 1;
|
|
|
@@ -202,14 +187,14 @@ sub position($$) {
|
|
|
if ($DEBUG) {
|
|
|
print STDERR 'Audio::position(): ' . $name . "\n";
|
|
|
}
|
|
|
- if (!loaded($name)) {
|
|
|
- die('Invalid QT document: ' . $name . "\n");
|
|
|
- }
|
|
|
+
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
|
|
|
if ($new) {
|
|
|
- runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
|
|
|
+ runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name} . ' to ' . $new);
|
|
|
} else {
|
|
|
- return runApplescript('tell application "QuickTime Player" to get current time of document ' . $FILES{$name}->{'name'});
|
|
|
+ return runApplescript('tell application "QuickTime Player" to get current time of document ' . $FILES{$name});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -219,134 +204,78 @@ sub rate($$) {
|
|
|
if ($DEBUG) {
|
|
|
print STDERR 'Audio::rate(): ' . $name . "\n";
|
|
|
}
|
|
|
- if (!loaded($name)) {
|
|
|
- die('Invalid QT document: ' . $name . "\n");
|
|
|
- }
|
|
|
+
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
|
|
|
if ($new) {
|
|
|
- runApplescript('tell application "QuickTime Player" to set rate of document ' . $FILES{$name}->{'name'} . ' to ' . $new);
|
|
|
+ runApplescript('tell application "QuickTime Player" to set rate of document ' . $FILES{$name} . ' to ' . $new);
|
|
|
} else {
|
|
|
- return runApplescript('tell application "QuickTime Player" to get rate of document ' . $FILES{$name}->{'name'});
|
|
|
+ return runApplescript('tell application "QuickTime Player" to get rate of document ' . $FILES{$name});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-# Pause the named document
|
|
|
-sub pause($) {
|
|
|
- my ($name) = @_;
|
|
|
+# Set or get a document's volume
|
|
|
+sub volume($$) {
|
|
|
+ my ($name, $new) = @_;
|
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Audio::pause(): ' . $name . "\n";
|
|
|
- }
|
|
|
- if (!loaded($name)) {
|
|
|
- die('Invalid QT document: ' . $name . "\n");
|
|
|
+ print STDERR 'Audio::volume(): ' . $name . "\n";
|
|
|
}
|
|
|
|
|
|
- # Pause playback
|
|
|
- runApplescript('tell application "QuickTime Player" to pause document ' . $FILES{$name}->{'name'});
|
|
|
-}
|
|
|
+ # Ensure the file is loaded
|
|
|
+ dieUnloaded($name);
|
|
|
|
|
|
-# Stop playback of the named document or all documents
|
|
|
-sub stop($) {
|
|
|
- my ($name) = @_;
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR "Audio::stopAudio()\n";
|
|
|
- }
|
|
|
-
|
|
|
- # Allow control of all documents or a specific document
|
|
|
- my $doc = 'every document';
|
|
|
- if (loaded($name)) {
|
|
|
- $doc = 'document ' . $FILES{$name}->{'name'};
|
|
|
+ if ($new) {
|
|
|
+ runApplescript('tell application "QuickTime Player" to set audio volume of document ' . $FILES{$name} . ' to ' . $new);
|
|
|
+ } else {
|
|
|
+ return runApplescript('tell application "QuickTime Player" to get audio volume of document ' . $FILES{$name});
|
|
|
}
|
|
|
-
|
|
|
- # Stop and rewind all QT documents
|
|
|
- runApplescript('tell application "QuickTime Player" to stop ' . $doc);
|
|
|
- runApplescript('tell application "QuickTime Player" to set current time of ' . $doc . ' to 0');
|
|
|
-
|
|
|
- # Bring Plex back to the front
|
|
|
- runApplescript('tell application "Plex" to activate');
|
|
|
}
|
|
|
|
|
|
-# Unload the named document
|
|
|
-sub unload($) {
|
|
|
- my ($name) = @_;
|
|
|
+# Set or get the system output volume
|
|
|
+sub systemVolume($) {
|
|
|
+ my ($new) = @_;
|
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Audio::loadAudio(): ' . $name . "\n";
|
|
|
- }
|
|
|
- if (!available($name)) {
|
|
|
- die('Invalid QT document: ' . $name . "\n");
|
|
|
+ print STDERR "Audio::systemVolume()\n";
|
|
|
}
|
|
|
|
|
|
- # Nothing to do if the file isn't loaded
|
|
|
- if (!loaded($name)) {
|
|
|
- return;
|
|
|
+ if ($new) {
|
|
|
+ runApplescript('set volume output volume ' . $new);
|
|
|
+ } else {
|
|
|
+ return runApplescript('get output volume of (get volume settings)');
|
|
|
}
|
|
|
-
|
|
|
- # Close to document
|
|
|
- runApplescript('tell application "QuickTime Player" to close document ' . $FILES{$name}->{'name'});
|
|
|
-
|
|
|
- # Delete our local handle
|
|
|
- delete($FILES{$name}->{'name'});
|
|
|
}
|
|
|
|
|
|
-# Load the named document
|
|
|
-sub load($) {
|
|
|
- my ($name) = @_;
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR 'Audio::loadAudio(): ' . $name . "\n";
|
|
|
- }
|
|
|
- if (!available($name)) {
|
|
|
- die('Invalid QT document: ' . $name . "\n");
|
|
|
- }
|
|
|
-
|
|
|
- # Unload first, if we're already loaded
|
|
|
- if (loaded($name)) {
|
|
|
- unload($name);
|
|
|
- }
|
|
|
-
|
|
|
- # Find out how many files are already open
|
|
|
- my $count = runApplescript('tell application "QuickTime Player" to count items of every document');
|
|
|
-
|
|
|
- # Open the file
|
|
|
- IPC::System::Simple::system('open', '-a', 'QuickTime Player', $FILES{$name}->{'path'});
|
|
|
-
|
|
|
- # Wait for QT to load the new file
|
|
|
- my @cmd = ('tell application "QuickTime Player"');
|
|
|
- push(@cmd, 'set endDate to current date + (3/60 * minutes)');
|
|
|
- push(@cmd, 'repeat while (count items of every document) <= ' . $count . ' and current date < endDate');
|
|
|
- push(@cmd, 'delay 0.1');
|
|
|
- push(@cmd, 'end repeat');
|
|
|
- push(@cmd, 'get document 1');
|
|
|
- push(@cmd, 'end tell');
|
|
|
- my $doc = runApplescript(join("\n", @cmd));
|
|
|
-
|
|
|
- # Clean up the document name
|
|
|
- $doc =~ s/^\s*document //;
|
|
|
- $doc =~ s/\s+$//;
|
|
|
- $doc =~ s/\"/\\\"/g;
|
|
|
- $doc = '"' . $doc . '"';
|
|
|
+# Stop playback on all documents
|
|
|
+sub stopAll() {
|
|
|
+ sendCmd('STOP');
|
|
|
+}
|
|
|
|
|
|
- # Save the document handle
|
|
|
- $FILES{$name}->{'name'} = $doc;
|
|
|
+# Reset the entire QT Player process
|
|
|
+sub reset() {
|
|
|
+ sendCmd('RESET');
|
|
|
+}
|
|
|
|
|
|
- # Bring Plex back to the front
|
|
|
- runApplescript('tell application "Plex" to activate');
|
|
|
+# Read and parse the qt_player file list
|
|
|
+sub parseList() {
|
|
|
+ %FILES = ();
|
|
|
+ open(my $fh, $QT_FILE)
|
|
|
+ or die('Unable to open QT_PLAYER file: ' . $! . "\n");
|
|
|
+ while (<$fh>) {
|
|
|
+ my ($name, $handle) = $_ =~ /^([^|]+)\|\s*(\".+\")\s*$/;
|
|
|
+ if (!$name || !$handle) {
|
|
|
+ die('Invalid QT_PLAYER line: ' . $_);
|
|
|
+ }
|
|
|
+ $FILES{$name} = $handle;
|
|
|
+ }
|
|
|
+ close($fh);
|
|
|
}
|
|
|
|
|
|
-# Get things into a reasonable state
|
|
|
-sub init() {
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR "Audio::init()\n";
|
|
|
+sub dieUnloaded($) {
|
|
|
+ my ($name) = @_;
|
|
|
+ if (!loaded($name)) {
|
|
|
+ die('File not loaded: ' . $name . "\n");
|
|
|
}
|
|
|
-
|
|
|
- # Close all QT documents and relaunch QT Player
|
|
|
- runApplescript('tell application "QuickTime Player" to close every document');
|
|
|
- runApplescript('tell application "QuickTime Player" to quit');
|
|
|
- sleep(1);
|
|
|
- IPC::System::Simple::system('open', '-a', 'QuickTime Player');
|
|
|
-
|
|
|
- # Always load and play the SILENCE file (for sanity and general init)
|
|
|
- add('SILENCE', 'DMX/Silence.wav');
|
|
|
- load('SILENCE');
|
|
|
- play('SILENCE');
|
|
|
}
|
|
|
|
|
|
# Always return true
|