Audio.pm 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use POSIX;
  5. use File::Basename;
  6. use IPC::System::Simple;
  7. use Time::Out;
  8. use Time::HiRes;
  9. # Package name
  10. package Audio;
  11. # Config
  12. my $MEDIA_PATH = `~/bin/video/mediaPath`;
  13. # App config
  14. my $DATA_DIR = DMX::dataDir();
  15. my $QT_SOCK = 'QT_PLAYER';
  16. my $QT_FILE = $DATA_DIR . $QT_SOCK;
  17. my $WAIT_DELAY = 0.1;
  18. my $MAX_WAIT = 5 / $WAIT_DELAY;
  19. my $TIMEOUT_AS = 10;
  20. # Debug
  21. my $DEBUG = 0;
  22. if ($ENV{'DEBUG'}) {
  23. $DEBUG = 1;
  24. }
  25. # State
  26. my %FILES = ();
  27. my $SOCK = undef();
  28. # Send the provided arguments to the QT socket
  29. sub sendCmd(@) {
  30. # Connect the socket as needed
  31. if (!$SOCK) {
  32. $SOCK = DMX::clientSock($QT_SOCK);
  33. }
  34. # Ensure the data is compatible with the encoding scheme
  35. foreach my $part (@_) {
  36. if ($part =~ /\|/) {
  37. die('Invalid QT command component: ' . $part . "\n");
  38. }
  39. }
  40. # Build the command stirng
  41. my $str = join('|', @_);
  42. if ($DEBUG) {
  43. print STDERR 'Sending QT_PLAYER command: ' . $str . "\n";
  44. }
  45. # Send the command
  46. $SOCK->send($str)
  47. or die("Unable to send to QT socket\n");
  48. }
  49. # Execute the provided string with osascript
  50. sub runApplescript($) {
  51. my ($script) = @_;
  52. if ($DEBUG) {
  53. print STDERR 'AppleScript: ' . $script . "\n";
  54. }
  55. # Die if operations take too long
  56. my $retval = Time::Out::timeout $TIMEOUT_AS => sub {
  57. return IPC::System::Simple::capture('osascript', '-e', $script);
  58. };
  59. if ($@) {
  60. die("Applescript execution timed out\n");
  61. }
  62. # Clean up the return value
  63. if (defined($retval)) {
  64. $retval =~ s/[\r\n]$//;
  65. if ($DEBUG) {
  66. print STDERR "\tAppleScript result: " . $retval . "\n";
  67. }
  68. }
  69. return $retval;
  70. }
  71. # Is the named document loaded?
  72. sub loaded($) {
  73. my ($name) = @_;
  74. parseList();
  75. return exists($FILES{$name});
  76. }
  77. # Load a document
  78. sub load($$) {
  79. my ($name, $path) = @_;
  80. # Unload as needed
  81. if (loaded($name)) {
  82. unload($name);
  83. }
  84. # Request load
  85. sendCmd('LOAD', $name, $path);
  86. # Wait for the load to register
  87. my $count = 0;
  88. while (!loaded($name)) {
  89. Time::HiRes::sleep($WAIT_DELAY);
  90. $count++;
  91. if ($count > $MAX_WAIT) {
  92. die('Unable to open file: ' . $name . "\n");
  93. }
  94. }
  95. }
  96. # Unload the named document
  97. sub unload($) {
  98. my ($name) = @_;
  99. if ($DEBUG) {
  100. print STDERR 'Audio::loadAudio(): ' . $name . "\n";
  101. }
  102. # Request unload
  103. sendCmd('UNLOAD', $name);
  104. # Wait for the unload to register
  105. my $count = 0;
  106. while (loaded($name)) {
  107. Time::HiRes::sleep($WAIT_DELAY);
  108. $count++;
  109. if ($count > $MAX_WAIT) {
  110. die('Unable to close file: ' . $name . "\n");
  111. }
  112. }
  113. }
  114. # Play the named document, waiting for completion
  115. sub play($) {
  116. my ($name) = @_;
  117. if ($DEBUG) {
  118. print STDERR 'Audio::play(): ' . $name . "\n";
  119. }
  120. # Ensure the file is loaded
  121. dieUnloaded($name);
  122. my @cmd = ('tell application "QuickTime Player"');
  123. push(@cmd, 'play document ' . $FILES{$name});
  124. push(@cmd, 'repeat while playing of document ' . $FILES{$name} . ' = true');
  125. push(@cmd, 'delay 0.1');
  126. push(@cmd, 'end repeat');
  127. push(@cmd, 'end tell');
  128. runApplescript(join("\n", @cmd));
  129. }
  130. # Play the named document and immediately return
  131. sub background($) {
  132. my ($name) = @_;
  133. if ($DEBUG) {
  134. print STDERR 'Audio::background(): ' . $name . "\n";
  135. }
  136. # Ensure the file is loaded
  137. dieUnloaded($name);
  138. runApplescript('tell application "QuickTime Player" to play document ' . $FILES{$name});
  139. }
  140. # Pause the named document
  141. sub pause($) {
  142. my ($name) = @_;
  143. if ($DEBUG) {
  144. print STDERR 'Audio::pause(): ' . $name . "\n";
  145. }
  146. # Ensure the file is loaded
  147. dieUnloaded($name);
  148. runApplescript('tell application "QuickTime Player" to pause document ' . $FILES{$name});
  149. }
  150. # Stop playback of the named document
  151. sub stop($) {
  152. my ($name) = @_;
  153. if ($DEBUG) {
  154. print STDERR "Audio::stopAudio()\n";
  155. }
  156. # Ensure the file is loaded
  157. dieUnloaded($name);
  158. # Stop and rewind
  159. runApplescript('tell application "QuickTime Player" to stop document ' . $FILES{$name});
  160. runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name} . ' to 0');
  161. }
  162. # Is the named document playing?
  163. sub playing($) {
  164. my ($name) = @_;
  165. if ($DEBUG) {
  166. print STDERR 'Audio::playing(): ' . $name . "\n";
  167. }
  168. # Ensure the file is loaded
  169. dieUnloaded($name);
  170. # QT has a "playing" property but it is only true when rate == 1.0
  171. my $rate = rate($name);
  172. if ($rate > 0) {
  173. return 1;
  174. }
  175. return 0;
  176. }
  177. # Set or get the audio position
  178. sub position($$) {
  179. my ($name, $new) = @_;
  180. if ($DEBUG) {
  181. print STDERR 'Audio::position(): ' . $name . "\n";
  182. }
  183. # Ensure the file is loaded
  184. dieUnloaded($name);
  185. if ($new) {
  186. runApplescript('tell application "QuickTime Player" to set current time of document ' . $FILES{$name} . ' to ' . $new);
  187. } else {
  188. return runApplescript('tell application "QuickTime Player" to get current time of document ' . $FILES{$name});
  189. }
  190. }
  191. # Set or get the playback rate
  192. sub rate($$) {
  193. my ($name, $new) = @_;
  194. if ($DEBUG) {
  195. print STDERR 'Audio::rate(): ' . $name . "\n";
  196. }
  197. # Ensure the file is loaded
  198. dieUnloaded($name);
  199. if ($new) {
  200. runApplescript('tell application "QuickTime Player" to set rate of document ' . $FILES{$name} . ' to ' . $new);
  201. } else {
  202. return runApplescript('tell application "QuickTime Player" to get rate of document ' . $FILES{$name});
  203. }
  204. }
  205. # Set or get a document's volume
  206. sub volume($$) {
  207. my ($name, $new) = @_;
  208. if ($DEBUG) {
  209. print STDERR 'Audio::volume(): ' . $name . "\n";
  210. }
  211. # Ensure the file is loaded
  212. dieUnloaded($name);
  213. if ($new) {
  214. runApplescript('tell application "QuickTime Player" to set audio volume of document ' . $FILES{$name} . ' to ' . $new);
  215. } else {
  216. return runApplescript('tell application "QuickTime Player" to get audio volume of document ' . $FILES{$name});
  217. }
  218. }
  219. # Set or get the system output volume
  220. sub systemVolume($) {
  221. my ($new) = @_;
  222. if ($DEBUG) {
  223. print STDERR "Audio::systemVolume()\n";
  224. }
  225. if ($new) {
  226. runApplescript('set volume output volume ' . $new);
  227. } else {
  228. return runApplescript('get output volume of (get volume settings)');
  229. }
  230. }
  231. # Stop playback on all documents
  232. sub stopAll() {
  233. sendCmd('STOP');
  234. }
  235. # Reset the entire QT Player process
  236. sub reset() {
  237. sendCmd('RESET');
  238. }
  239. # Read and parse the qt_player file list
  240. sub parseList() {
  241. %FILES = ();
  242. open(my $fh, $QT_FILE)
  243. or die('Unable to open QT_PLAYER file: ' . $! . "\n");
  244. while (<$fh>) {
  245. my ($name, $handle) = $_ =~ /^([^|]+)\|\s*(\".+\")\s*$/;
  246. if (!$name || !$handle) {
  247. die('Invalid QT_PLAYER line: ' . $_);
  248. }
  249. $FILES{$name} = $handle;
  250. }
  251. close($fh);
  252. }
  253. sub dieUnloaded($) {
  254. my ($name) = @_;
  255. if (!loaded($name)) {
  256. die('File not loaded: ' . $name . "\n");
  257. }
  258. }
  259. # Always return true
  260. 1;