summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Arthur <flowerysong00@yahoo.com>2013-01-15 00:56:42 -0500
committerPaul Arthur <flowerysong00@yahoo.com>2013-01-15 11:30:47 -0500
commite2ca05d5b419944adb3723ab0253c7c35418a0e4 (patch)
treef4d0635b3690cd6c060acf69c3afebc8a7e67284
parente2484ee9a0e7f7de16fe2b3d015af59f0c9111c0 (diff)
downloadampache-e2ca05d5b419944adb3723ab0253c7c35418a0e4.tar.gz
ampache-e2ca05d5b419944adb3723ab0253c7c35418a0e4.tar.bz2
ampache-e2ca05d5b419944adb3723ab0253c7c35418a0e4.zip
Make playlist downloads idempotent
Should fix the VLC plugin, as well as allow direct use of an Ampache site on Android devices. First, split the Stream class into an instantiable class that does the playlist wrangling and a static class that handles the streaming stuff. How does this work? Well, stream.php does its fancy stuff like gathering the media IDs and clearing the playlist, but instead of generating the playlist file there we use the Stream_Playlist class to store the list of URLs in the database, then redirect to play/index.php to create the actual download (there are some magic playlist types like localplay that don't need to redirect.) The playlist will be cached as long as that stream session is active, so it can be downloaded multiple times and by clients that don't share the browser's cookie cache. Clean up the playlist generation by reducing copypasta.
-rwxr-xr-xdocs/CHANGELOG2
-rw-r--r--lib/class/stream.class.php464
-rw-r--r--lib/class/stream_playlist.class.php451
-rw-r--r--lib/class/update.class.php25
-rw-r--r--modules/flash/xspf_player.php5
-rw-r--r--play/index.php23
-rw-r--r--stream.php18
7 files changed, 522 insertions, 466 deletions
diff --git a/docs/CHANGELOG b/docs/CHANGELOG
index 1b7ff2d4..a839d76a 100755
--- a/docs/CHANGELOG
+++ b/docs/CHANGELOG
@@ -4,6 +4,8 @@
--------------------------------------------------------------------------
v.3.6-FUTURE
+ - Fixed streaming on Android devices and anything else that expects to
+ be able to pass a playlist URL to an application and have it work
- Removed the SHOUTcast localplay controller
--------------------------------------------------------------------------
diff --git a/lib/class/stream.class.php b/lib/class/stream.class.php
index adcfe87b..293e0f88 100644
--- a/lib/class/stream.class.php
+++ b/lib/class/stream.class.php
@@ -1,8 +1,6 @@
<?php
/* vim:set tabstop=8 softtabstop=8 shiftwidth=8 noexpandtab: */
/**
- * Stream Class
- *
*
* LICENSE: GNU General Public License, version 2 (GPLv2)
* Copyright (c) 2001 - 2011 Ampache.org All Rights Reserved
@@ -20,97 +18,17 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
- * @package Ampache
- * @copyright 2001 - 2011 Ampache.org
- * @license http://opensource.org/licenses/gpl-2.0 GPLv2
- * @link http://www.ampache.org/
*/
-/**
- * Stream Class
- *
- * This class is used to generate the Playlists and pass them on
- * With Localplay this actually just sends the commands to the localplay
- * module in question. It has two sources for data
- * songs (array of ids) and urls (array of full urls)
- *
- * @package Ampache
- * @copyright 2001 - 2011 Ampache.org
- * @license http://opensource.org/licenses/gpl-2.0 GPLv2
- * @link http://www.ampache.org/
- */
-class Stream {
- /* Variables from DB */
- public $type;
- public $media = array();
- public $urls = array();
- public $user_id;
+class Stream {
- // Generate once an object is constructed
public static $session;
-
- // Let's us tell if the session has been activated
private static $session_inserted;
- /**
- * Constructor for the stream class takes a type and an array
- * of song ids
- */
- public function __construct($type='m3u', $media_ids) {
-
- $this->type = $type;
- $this->media = $media_ids;
- $this->user_id = $GLOBALS['user']->id;
-
- if (!is_array($this->media)) { settype($this->media,'array'); }
-
- } // Constructor
-
- /**
- * start
- *runs this and depending on the type passed it will
- *call the correct function
- */
- public function start() {
-
- if (!count($this->media) AND !count($this->urls)) {
- debug_event('stream','Error: No Songs Passed on ' . $this->type . ' stream','2');
- return false;
- }
-
- // We're starting insert the session into session_stream
- if (!self::get_session()) {
- debug_event('stream','Session Insertion failure, aborting','3');
- return false;
- }
-
- $methods = get_class_methods('Stream');
- $create_function = "create_" . $this->type;
-
- // If in the class, call it
- if (in_array($create_function,$methods)) {
- $this->{$create_function}();
- }
- // Assume M3u incase they've pooched the type
- else {
- $this->create_m3u();
- }
-
- } // start
-
- /**
- * add_urls
- * Add an array of urls, it may be a single one who knows, this
- * is used for things that aren't coming from media objects
- */
- public function add_urls($urls=array()) {
-
- if (!is_array($urls)) { return false; }
-
- $this->urls = array_merge($urls,$this->urls);
-
- } // manual_url_add
+ private function __construct() {
+ // Static class, do nothing.
+ }
/**
* get_session
@@ -184,8 +102,10 @@ class Stream {
/**
* gc_session
- * This function performes the garbage collection stuff, run on extend and on now playing refresh
- * There is an array of agents that we will never GC because of their nature, MPD being the best example
+ * This function performes the garbage collection stuff, run on extend
+ * and on now playing refresh.
+ * There is an array of agents that we will never GC because of their
+ * nature, MPD being the best example.
*/
public static function gc_session($ip='',$agent='',$uid='',$sid='') {
@@ -195,6 +115,8 @@ class Stream {
$sql = "DELETE FROM `session_stream` WHERE `expire` < '$time'";
$db_results = Dba::write($sql);
+ Stream_Playlist::clean();
+
foreach ($append_array as $append_agent) {
if (strpos(strtoupper($agent), $append_agent) !== false) {
// We're done here jump ship!
@@ -234,372 +156,6 @@ class Stream {
} // extend_session
/**
- * create_simplem3u
- * this creates a simple m3u without any of the extended information
- */
- public function create_simple_m3u() {
-
- header("Cache-control: public");
- header("Content-Disposition: filename=ampache_playlist.m3u");
- header("Content-Type: audio/x-mpegurl;");
-
- // Flip for the poping!
- asort($this->urls);
-
- /* Foreach songs */
- foreach ($this->media as $element) {
- $type = array_shift($element);
- echo call_user_func(array($type,'play_url'),array_shift($element)) . "\n";
- } // end foreach
-
- /* Foreach the additional URLs */
- foreach ($this->urls as $url) {
- echo "$url\n";
- }
-
- } // simple_m3u
-
- /**
- * create_m3u
- * creates an m3u file, this includes the EXTINFO and as such can be
- * large with very long playlsits
- */
- public function create_m3u() {
-
- // Send the client an m3u playlist
- header("Cache-control: public");
- header("Content-Disposition: filename=ampache_playlist.m3u");
- header("Content-Type: audio/x-mpegurl;");
- echo "#EXTM3U\n";
-
- // Foreach the songs in this stream object
- foreach ($this->media as $element) {
- $type = array_shift($element);
- $media = new $type(array_shift($element));
- $media->format();
- switch ($type) {
- case 'song':
- echo "#EXTINF:$media->time," . $media->f_artist_full . " - " . $media->title . "\n";
- break;
- case 'video':
- echo "#EXTINF: Video - $media->title\n";
- break;
- case 'radio':
- echo "#EXTINF: Radio - $media->name [$media->frequency] ($media->site_url)\n";
- break;
- case 'random':
- echo "#EXTINF:Random URL\n";
- break;
- default:
- echo "#EXTINF:URL-Add\n";
- break;
- }
- echo call_user_func(array($type,'play_url'),$media->id) . "\n";
- } // end foreach
-
- /* Foreach URLS */
- foreach ($this->urls as $url) {
- echo "#EXTINF: URL-Add\n";
- echo $url . "\n";
- }
-
- } // create_m3u
-
- /**
- * create_pls
- * This creates a new pls file from an array of songs and
- * urls, exciting I know
- */
- public function create_pls() {
-
- /* Count entries */
- $total_entries = count($this->media) + count($this->urls);
-
- // Send the client a pls playlist
- header("Cache-control: public");
- header("Content-Disposition: filename=ampache_playlist.pls");
- header("Content-Type: audio/x-scpls;");
- echo "[playlist]\n";
- echo "NumberOfEntries=$total_entries\n";
- foreach ($this->media as $element) {
- $i++;
- $type = array_shift($element);
- $media = new $type(array_shift($element));
- $media->format();
- switch ($type) {
- case 'song':
- $name = $media->f_artist_full . " - " . $media->title . "." . $media->type;
- $length = $media->time;
- break;
- default:
- $name = 'URL-Add';
- $length='-1';
- break;
- }
-
- $url = call_user_func(array($type,'play_url'),$media->id);
- echo "File" . $i . "=$url\n";
- echo "Title" . $i . "=$name\n";
- echo "Length" . $i . "=$length\n";
- } // end foreach songs
-
- /* Foreach Additional URLs */
- foreach ($this->urls as $url) {
- $i++;
- echo "File" . $i ."=$url\n";
- echo "Title". $i . "=AddedURL\n";
- echo "Length" . $i . "=-1\n";
- } // end foreach urls
-
- echo "Version=2\n";
-
- } // create_pls
-
- /**
- * create_asx
- * creates an ASX playlist (Thx Samir Kuthiala) This should really only be used
- * if all of the content is ASF files.
- */
- public function create_asx() {
-
- header("Cache-control: public");
- header("Content-Disposition: filename=ampache_playlist.asx");
- header("Content-Type: video/x-ms-wmv;");
-
- echo "<ASX version = \"3.0\" BANNERBAR=\"AUTO\">\n";
- echo "<TITLE>Ampache ASX Playlist</TITLE>";
-
- foreach ($this->media as $element) {
- $type = array_shift($element);
- $media = new $type(array_shift($element));
- $media->format();
- switch ($type) {
- case 'song':
- $name = $media->f_album_full . " - " . $media->title . "." . $media->type;
- $author = $media->f_artist_full;
- break;
- default:
- $author = 'Ampache';
- $name = 'URL-Add';
- break;
- } // end switch
- $url = call_user_func(array($type,'play_url'),$media->id);
-
- echo "<ENTRY>\n";
- echo "<TITLE>$name</TITLE>\n";
- echo "<AUTHOR>$author</AUTHOR>\n";
- echo "\t\t<COPYRIGHT>".$media->year."</COPYRIGHT>\n";
- echo "\t\t<DURATION VALUE=\"00:00:".$media->time."\" />\n";
- echo "\t\t<PARAM NAME=\"Album\" Value=\"".$media->f_album_full."\" />\n";
- echo "\t\t<PARAM NAME=\"Genre\" Value=\"".$media->get_genre_name()."\" />\n";
- echo "\t\t<PARAM NAME=\"Composer\" Value=\"".$media->f_artist_full."\" />\n";
- echo "\t\t<PARAM NAME=\"Prebuffer\" Value=\"false\" />\n";
- echo "<REF HREF = \"". $url . "\" />\n";
- echo "</ENTRY>\n";
-
- } // end foreach
-
- /* Foreach urls */
- foreach ($this->urls as $url) {
- echo "<ENTRY>\n";
- echo "<TITLE>AddURL</TITLE>\n";
- echo "<AUTHOR>AddURL</AUTHOR>\n";
- echo "<REF HREF=\"$url\" />\n";
- echo "</ENTRY>\n";
- } // end foreach
-
- echo "</ASX>\n";
-
- } // create_asx
-
- /**
- * create_xspf
- * creates an XSPF playlist (Thx PB1DFT)
- */
- public function create_xspf() {
-
- // Itterate through the songs
- foreach ($this->media as $element) {
- $type = array_shift($element);
- $media = new $type(array_shift($element));
- $media->format();
-
- $xml = array();
-
- switch ($type) {
- default:
- case 'song':
- $xml['track']['title'] = $media->title;
- $xml['track']['creator'] = $media->f_artist_full;
- $xml['track']['info'] = Config::get('web_path') . "/albums.php?action=show&album=" . $media->album;
- $xml['track']['image'] = Config::get('web_path') . "/image.php?id=" . $media->album . "&thumb=3";
- $xml['track']['album'] = $media->f_album_full;
- $length = $media->time;
- break;
- case 'video':
- $xml['track']['title'] = $media->title;
- $xml['track']['creator'] = $media->f_artist_full;
- $xml['track']['info'] = Config::get('web_path') . '/browse.php?action=video';
- $xml['track']['image'] = Config::get('web_path') . '/image.php?id=' . $media->id . '&type=video&thumb=3&sid=' . session_id();
- $xml['track']['meta'] = array('attribute'=>'rel="provider"','value'=>'video');
- break;
- } // type
-
- $xml['track']['location'] = call_user_func(array($type,'play_url'),$media->id);
- $xml['track']['identifier'] = $xml['track']['location'];
- $xml['track']['duration'] = $length * 1000;
-
- $result .= xmlData::keyed_array($xml,1);
-
- } // end foreach
-
- xmlData::set_type('xspf');
-
- header("Cache-control: public");
- header("Content-Disposition: filename=ampache_playlist.xspf");
- header("Content-Type: application/xspf+xml; charset=utf-8");
- echo xmlData::header();
- echo $result;
- echo xmlData::footer();
-
- } // create_xspf
-
- /**
- * create_xspf_player
- * due to the fact that this is an integrated player (flash) we actually
- * have to do a little 'cheating' to make this work, we are going to take
- * advantage of tmp_playlists to do all of this hotness
- */
- public function create_xspf_player() {
-
- /* Build the extra info we need to have it pass */
- $play_info = "?action=show&tmpplaylist_id=" . $GLOBALS['user']->playlist->id;
-
- // start ugly evil javascript code
- //FIXME: This needs to go in a template, here for now though
- //FIXME: This preference doesn't even exists, we'll eventually
- //FIXME: just make it the default
- if (Config::get('embed_xspf') == 1 ){
- header("Location: ".Config::get('web_path')."/index.php?xspf&play_info=".$GLOBALS['user']->playlist->id);
- }
- else {
- echo "<html><head>\n";
- echo "<title>" . Config::get('site_title') . "</title>\n";
- echo "<script language=\"javascript\" type=\"text/javascript\">\n";
- echo "<!-- begin\n";
- echo "function PlayerPopUp(URL) {\n";
- // We do a little check here to see if it's a Wii!
- if (false !== stristr($_SERVER['HTTP_USER_AGENT'], 'Nintendo Wii')) {
- echo "window.location=URL;\n";
- }
- // Else go ahead and do the normal stuff
- else {
- echo "window.open(URL, 'XSPF_player', 'width=400,height=170,scrollbars=0,toolbar=0,location=0,directories=0,status=0,resizable=0');\n";
- echo "window.location = '" . return_referer() . "';\n";
- echo "return false;\n";
- }
- echo "}\n";
- echo "// end -->\n";
- echo "</script>\n";
- echo "</head>\n";
-
- echo "<body onLoad=\"javascript:PlayerPopUp('" . Config::get('web_path') . "/modules/flash/xspf_player.php" . $play_info . "')\">\n";
- echo "</body>\n";
- echo "</html>\n";
- }
- } // create_xspf_player
-
- /**
- * create_localplay
- * This calls the Localplay API and attempts to
- * add, and then start playback
- */
- public function create_localplay() {
-
- // First figure out what their current one is and create the object
- $localplay = new Localplay(Config::get('localplay_controller'));
- $localplay->connect();
- foreach ($this->media as $element) {
- $type = array_shift($element);
- switch ($type) {
- case 'video':
- // Add check for video support
- case 'song':
- case 'radio':
- case 'random':
- $media = new $type(array_shift($element));
- break;
- default:
- $media = array_shift($element);
- break;
- } // switch on types
- $localplay->add($media);
- } // foreach object
-
- /**
- * Add urls after the fact
- */
- foreach ($this->urls as $url) {
- $localplay->add($url);
- }
-
- $localplay->play();
-
- } // create_localplay
-
- /**
- * create_democratic
- * This 'votes' on the songs it inserts them into
- * a tmp_playlist with user of -1 (System)
- */
- public function create_democratic() {
-
- $democratic = Democratic::get_current_playlist();
- $democratic->set_parent();
- $democratic->add_vote($this->media);
-
- } // create_democratic
-
- /**
- * create_download
- * This prompts for a download of the song, only a single
- * element can by in song_ids
- */
- private function create_download() {
-
- // There should only be one here...
- foreach ($this->media as $element) {
- $type = array_shift($element);
- $media = new $type(array_shift($element));
- $url = call_user_func(array($type,'play_url'),$media->id);
-
- // Append the fact we are downloading
- $url .= '&action=download';
-
- // Header redirect baby!
- header("Location: $url");
- exit;
- }
-
- } //create_download
-
- /**
- * create_ram
- *this functions creates a RAM file for use by Real Player
- */
- public function create_ram() {
-
- header("Cache-control: public");
- header("Content-Disposition: filename=ampache_playlist.ram");
- header("Content-Type: audio/x-pn-realaudio ram;");
- foreach ($this->media as $element) {
- $type = array_shift($element);
- echo $url = call_user_func(array($type,'play_url'),array_shift($element)) . "\n";
- } // foreach songs
-
- } // create_ram
-
- /**
* start_transcode
*
* This is a rather complex function that starts the transcoding or
diff --git a/lib/class/stream_playlist.class.php b/lib/class/stream_playlist.class.php
new file mode 100644
index 00000000..1a34f0b9
--- /dev/null
+++ b/lib/class/stream_playlist.class.php
@@ -0,0 +1,451 @@
+<?php
+/* vim:set tabstop=8 softtabstop=8 shiftwidth=8 noexpandtab: */
+/**
+ *
+ * LICENSE: GNU General Public License, version 2 (GPLv2)
+ * Copyright (c) 2001 - 2011 Ampache.org All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License v2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/**
+ * Stream_Playlist Class
+ *
+ * This class is used to generate the Playlists and pass them on
+ * With Localplay this actually just sends the commands to the localplay
+ * module in question.
+ */
+
+class Stream_Playlist {
+
+ public $id;
+ public $urls = array();
+ public $user;
+
+ /**
+ * Stream_Playlist constructor
+ * If an ID is passed, it should be a stream session ID.
+ */
+ public function __construct($id = null) {
+
+ if($id) {
+ Stream::set_session($id);
+ }
+
+ $this->id = Dba::escape(Stream::get_session());
+
+ if (!Stream::session_exists($this->id)) {
+ debug_event('stream_playlist', 'Stream::session_exists failed', 2);
+ return false;
+ }
+
+ $this->user = intval($GLOBALS['user']->id);
+
+ $sql = "SELECT * FROM `stream_playlist` WHERE `sid`='" .
+ $this->id . "' ORDER BY `id`";
+
+ $db_results = Dba::read($sql);
+
+ while ($row = Dba::fetch_assoc($db_results)) {
+ $this->urls[] = new Stream_URL($row);
+ }
+
+ return true;
+ }
+
+ private function _add_url($url) {
+ $this->urls[] = $url;
+
+ $sql = 'INSERT INTO `stream_playlist` ';
+
+ $fields[] = '`sid`';
+ $values[] = Dba::escape($this->id);
+
+ foreach ($url->properties as $field) {
+ if ($url->$field) {
+ $fields[] = '`' . $field . '`';
+ $values[] = Dba::escape($url->$field);
+ }
+ }
+ $sql .= '(' . implode(', ', $fields) . ') ';
+ $sql .= "VALUES('" . implode("', '", $values) . "')";
+
+ return Dba::write($sql);
+ }
+
+ public static function clean() {
+ $sql = 'DELETE FROM `stream_playlist` ' .
+ 'USING `stream_playlist` LEFT JOIN `session_stream` ' .
+ 'ON `session_stream`.`id`=`stream_playlist`.`sid` ' .
+ 'WHERE `session_stream`.`id` IS NULL';
+ return Dba::write($sql);
+ }
+
+ /**
+ * _media_to_urlarray
+ * Formats the URL and media information and adds it to the object
+ */
+ private static function _media_to_urlarray($media) {
+ $urls = array();
+ foreach($media as $medium) {
+ debug_event('stream_playlist', 'Adding ' . json_encode($media), 5);
+ $url = array();
+
+ $type = $medium['object_type'];
+ $array['type'] = $type;
+
+ $object = new $type($medium['object_id']);
+ $object->format();
+ //FIXME: play_url shouldn't be static
+ $url['url'] = $type::play_url($object->id);
+
+ // Set a default which can be overridden
+ $url['author'] = 'Ampache';
+ $url['time'] = $object->time;
+ switch($type) {
+ case 'song':
+ $url['title'] = $object->title;
+ $url['author'] = $object->f_artist_full;
+ $url['info_url'] = $object->f_link;
+ $url['image_url'] = Art::url($object->album, 'album');
+ $url['album'] = $object->f_album_full;
+ break;
+ case 'video':
+ $url['title'] = 'Video - ' . $object->title;
+ $url['author'] = $object->f_artist_full;
+ break;
+ case 'radio':
+ $url['title'] = 'Radio - ' . $object->name .
+ ' [' . $object->frequency .
+ '] (' . $object->site_url . ')';
+ break;
+ case 'random':
+ $url['title'] = 'Random URL';
+ break;
+ default:
+ $url['title'] = 'URL-Add';
+ $url['time'] = -1;
+ break;
+ }
+
+ $urls[] = new Stream_URL($url);
+ }
+
+ return $urls;
+ }
+
+ public function generate_playlist($type, $redirect = false) {
+
+ if (!count($this->urls)) {
+ debug_event('stream_playlist', 'Error: Empty URL array for ' . $this->id, 2);
+ return false;
+ }
+
+ debug_event('stream_playlist', 'generating a ' . $type, 5);
+
+ $ext = $type;
+ switch($type) {
+ case 'democratic':
+ case 'localplay':
+ case 'xspf_player':
+ // These are valid, but witchy
+ $redirect = false;
+ unset($ext);
+ break;
+ case 'asx':
+ $ct = 'video/x-ms-wmv';
+ break;
+ case 'pls':
+ $ct = 'audio/x-scpls';
+ break;
+ case 'ram':
+ $ct = 'audio/x-pn-realaudio ram';
+ break;
+ case 'simple_m3u':
+ $ext = 'm3u';
+ $ct = 'audio/x-mpegurl';
+ break;
+ case 'xspf':
+ $ct = 'application/xspf+xml';
+ break;
+ case 'm3u':
+ default:
+ // Assume M3U if the pooch is screwed
+ $ext = $type = 'm3u';
+ $ct = 'audio/x-mpegurl';
+ break;
+ }
+
+ if ($redirect) {
+ // Our ID is the SID, so we always want to include it
+ Config::set('require_session', true, true);
+ header('Location: ' . Stream::get_base_url() . 'uid=' . scrub_out($this->user) . '&type=playlist&playlist_type=' . scrub_out($type));
+ exit;
+ }
+
+ if (isset($ext)) {
+ header('Cache-control: public');
+ header('Content-Disposition: filename=ampache_playlist.' . $ext);
+ header('Content-Type: ' . $ct . ';');
+ }
+
+ $this->{'create_' . $type}();
+ }
+
+ /**
+ * add
+ * Adds an array of media
+ */
+ public function add($media = array()) {
+ $urls = $this->_media_to_urlarray($media);
+ foreach ($urls as $url) {
+ $this->_add_url($url);
+ }
+ }
+
+ /**
+ * add_urls
+ * Add an array of urls. This is used for things that aren't coming
+ * from media objects
+ */
+ public function add_urls($urls = array()) {
+
+ if (!is_array($urls)) { return false; }
+
+ foreach ($urls as $url) {
+ $this->_add_url(new Stream_URL(array(
+ 'url' => $url,
+ 'title' => 'URL-Add',
+ 'author' => 'Ampache',
+ 'time' => '-1'
+ )));
+ }
+ }
+
+ /**
+ * create_simplem3u
+ * this creates a simple m3u without any of the extended information
+ */
+ public function create_simple_m3u() {
+
+ foreach ($this->urls as $url) {
+ echo $url->url . "\n";
+ }
+
+ } // simple_m3u
+
+ /**
+ * create_m3u
+ * creates an m3u file, this includes the EXTINFO and as such can be
+ * large with very long playlsits
+ */
+ public function create_m3u() {
+
+ echo "#EXTM3U\n";
+
+ foreach ($this->urls as $url) {
+ echo '#EXTINF:' . $url->time, ',' . $url->author .
+ ' - ' . $url->title . "\n";
+ echo $url->url . "\n";
+ }
+
+ } // create_m3u
+
+ /**
+ * create_pls
+ */
+ public function create_pls() {
+
+ echo "[playlist]\n";
+ echo 'NumberOfEntries=' . count($this->urls) . "\n";
+ foreach ($this->urls as $url) {
+ $i++;
+ echo 'File' . $i . '='. $url->url . "\n";
+ echo 'Title' . $i . '=' . $url->author . ' - ' .
+ $url->title . "\n";
+ echo 'Length' . $i . '=' . $url->time . "\n";
+ }
+
+ echo "Version=2\n";
+ } // create_pls
+
+ /**
+ * create_asx
+ * This should really only be used if all of the content is ASF files.
+ */
+ public function create_asx() {
+
+ echo '<ASX version = "3.0" BANNERBAR="AUTO">' . "\n";
+ echo "<TITLE>Ampache ASX Playlist</TITLE>";
+
+ foreach ($this->urls as $url) {
+ echo "<ENTRY>\n";
+ echo '<TITLE>' . $url->title . "</TITLE>\n";
+ echo '<AUTHOR>' . $url->author . "</AUTHOR>\n";
+ echo "\t\t" . '<DURATION VALUE="00:00:' . $url->time . '" />' . "\n";
+ echo "\t\t" . '<PARAM NAME="Album" Value="' . $url->album . '" />' . "\n";
+ echo "\t\t" . '<PARAM NAME="Composer" Value="' . $url->author . '" />' . "\n";
+ echo "\t\t" . '<PARAM NAME="Prebuffer" Value="false" />' . "\n";
+ echo '<REF HREF = "' . $url->url . '" />' . "\n";
+ echo "</ENTRY>\n";
+ }
+
+ echo "</ASX>\n";
+
+ } // create_asx
+
+ /**
+ * create_xspf
+ */
+ public function create_xspf() {
+
+ foreach ($this->urls as $url) {
+ $xml = array();
+
+ $xml['track'] = array(
+ 'title' => $url->title,
+ 'creator' => $url->author,
+ // FIXME: regression
+ // video: ['meta'] = array('attribute'=>'rel="provider"','value'=>'video')
+ 'duration' => $url->time * 1000,
+ 'location' => $url->url,
+ 'identifier' => $url->url
+ );
+ if ($url->info_url) {
+ $xml['track']['info'] = $url->info_url;
+ }
+ if ($url->image_url) {
+ $xml['track']['image'] = $url->image_url;
+ }
+ if ($url->album) {
+ $xml['track']['album'] = $url->album;
+ }
+
+ $result .= xmlData::keyed_array($xml, true);
+
+ } // end foreach
+
+ xmlData::set_type('xspf');
+ echo xmlData::header();
+ echo $result;
+ echo xmlData::footer();
+
+ } // create_xspf
+
+ /**
+ * create_xspf_player
+ * Due to the fact that this is an integrated player (flash) we actually
+ * have to do a little 'cheating' to make this work.
+ * We are going to take advantage of tmp_playlists to do all of this
+ * hotness
+ */
+ public function create_xspf_player() {
+ debug_event('stream_playlist', 'Creating XSPF player', 5);
+ /* Build the extra info we need to have it pass */
+ $play_info = "?action=show&tmpplaylist_id=" . $GLOBALS['user']->playlist->id;
+
+ // start ugly evil javascript code
+ //FIXME: This needs to go in a template, here for now though
+ //FIXME: This preference doesn't even exists, we'll eventually
+ //FIXME: just make it the default
+ if (Config::get('embed_xspf') == 1 ){
+ header("Location: ".Config::get('web_path')."/index.php?xspf&play_info=".$GLOBALS['user']->playlist->id);
+ }
+ else {
+ echo "<html><head>\n";
+ echo "<title>" . Config::get('site_title') . "</title>\n";
+ echo "<script language=\"javascript\" type=\"text/javascript\">\n";
+ echo "<!-- begin\n";
+ echo "function PlayerPopUp(URL) {\n";
+ // We do a little check here to see if it's a Wii!
+ if (false !== stristr($_SERVER['HTTP_USER_AGENT'], 'Nintendo Wii')) {
+ echo "window.location=URL;\n";
+ }
+ // Else go ahead and do the normal stuff
+ else {
+ echo "window.open(URL, 'XSPF_player', 'width=400,height=170,scrollbars=0,toolbar=0,location=0,directories=0,status=0,resizable=0');\n";
+ echo "window.location = '" . return_referer() . "';\n";
+ echo "return false;\n";
+ }
+ echo "}\n";
+ echo "// end -->\n";
+ echo "</script>\n";
+ echo "</head>\n";
+
+ echo "<body onLoad=\"javascript:PlayerPopUp('" . Config::get('web_path') . "/modules/flash/xspf_player.php" . $play_info . "')\">\n";
+ echo "</body>\n";
+ echo "</html>\n";
+ }
+ } // create_xspf_player
+
+ /**
+ * create_localplay
+ * This calls the Localplay API to add the URLs and then start playback
+ */
+ public function create_localplay() {
+
+ $localplay = new Localplay(Config::get('localplay_controller'));
+ $localplay->connect();
+ foreach ($this->urls as $url) {
+ $localplay->add_url($url);
+ }
+
+ $localplay->play();
+
+ } // create_localplay
+
+ /**
+ * create_democratic
+ * This 'votes' on the songs it inserts them into
+ * a tmp_playlist with user of -1 (System)
+ */
+ public function create_democratic() {
+
+ $democratic = Democratic::get_current_playlist();
+ $democratic->set_parent();
+ $democratic->add_vote($this->media);
+
+ } // create_democratic
+
+ /**
+ * create_download
+ * This prompts for a download of the song
+ */
+ private function create_download() {
+
+ // There should only be one here...
+ if (count($this->urls) != 1) {
+ debug_event('stream_playlist', 'Download called, but $urls contains ' . json_encode($this->urls), 2);
+ }
+
+ // Header redirect baby!
+ $url = current($this->urls);
+ header('Location: ' . $url->url . '&action=download');
+ exit;
+ } //create_download
+
+ /**
+ * create_ram
+ *this functions creates a RAM file for use by Real Player
+ */
+ public function create_ram() {
+ foreach ($this->urls as $url) {
+ echo $url->url . "\n";
+ }
+ } // create_ram
+
+}
+
+?>
diff --git a/lib/class/update.class.php b/lib/class/update.class.php
index 31446e76..7a32c776 100644
--- a/lib/class/update.class.php
+++ b/lib/class/update.class.php
@@ -377,6 +377,9 @@ class Update {
$update_string = '- Allow compound MBIDs in the artist table.<br />';
$version[] = array('version' => '360010', 'description' => $update_string);
+ $update_string = '- Add table to store stream session playlist.<br />';
+ $version[] = array('version' => '360011', 'description' => $update_string);
+
return $version;
} // populate_version
@@ -2089,5 +2092,27 @@ class Update {
self::set_version('db_version', '360010');
}
+ /**
+ * update_380011
+ * We need a place to store actual playlist data for downloadable
+ * playlist files.
+ */
+ public static function update_360011() {
+ $sql = 'CREATE TABLE `stream_playlist` (' .
+ '`id` int(11) unsigned NOT NULL AUTO_INCREMENT,' .
+ '`sid` varchar(64) COLLATE utf8_unicode_ci NOT NULL,' .
+ '`url` text COLLATE utf8_unicode_ci NOT NULL,' .
+ '`info_url` text COLLATE utf8_unicode_ci DEFAULT NULL,' .
+ '`image_url` text COLLATE utf8_unicode_ci DEFAULT NULL,' .
+ '`title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,' .
+ '`author` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,' .
+ '`album` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,' .
+ '`type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,' .
+ '`time` smallint(5) DEFAULT NULL,' .
+ 'PRIMARY KEY (`id`), KEY `sid` (`sid`))';
+ $db_results = Dba::write($sql);
+ self::set_version('db_version', '360011');
+ }
+
} // end update class
?>
diff --git a/modules/flash/xspf_player.php b/modules/flash/xspf_player.php
index 42d9bdf6..9f9dde2e 100644
--- a/modules/flash/xspf_player.php
+++ b/modules/flash/xspf_player.php
@@ -35,8 +35,9 @@ switch ($_REQUEST['action']) {
// Set for hackage!
$_REQUEST['flash_hack'] = 1;
$objects = $GLOBALS['user']->playlist->get_items();
- $stream = new Stream('xspf',$objects);
- $stream->start();
+ $stream = new Stream_Playlist();
+ $stream->add($objects);
+ $stream->generate_playlist('xspf', false);
break;
case 'show':
$play_url = Config::get('web_path') . '/modules/flash/xspf_player.php';
diff --git a/play/index.php b/play/index.php
index e7c3917c..fb50a6ce 100644
--- a/play/index.php
+++ b/play/index.php
@@ -39,10 +39,19 @@ ob_end_clean();
/* These parameters had better come in on the url. */
$uid = scrub_in($_REQUEST['uid']);
-$oid = $_REQUEST['song'] ? scrub_in($_REQUEST['song']) : scrub_in($_REQUEST['oid']);
+$oid = $_REQUEST['oid']
+ // FIXME: Any place that doesn't use oid should be fixed
+ ? scrub_in($_REQUEST['oid'])
+ : scrub_in($_REQUEST['song']);
$sid = scrub_in($_REQUEST['ssid']);
$xml_rpc = scrub_in($_REQUEST['xml_rpc']);
$video = make_bool($_REQUEST['video']);
+$type = scrub_in($_REQUEST['type']);
+
+if ($type == 'playlist') {
+ $playlist_type = scrub_in($_REQUEST['playlist_type']);
+ $oid = $sid;
+}
/* This is specifically for tmp playlist requests */
$demo_id = scrub_in($_REQUEST['demo_id']);
@@ -120,6 +129,18 @@ if (Config::get('access_control')) {
}
} // access_control is enabled
+// Handle playlist downloads
+if ($type == 'playlist') {
+ $playlist = new Stream_Playlist($oid);
+ // Some rudimentary security
+ if ($uid != $playlist->user) {
+ access_denied();
+ exit;
+ }
+ $playlist->generate_playlist($playlist_type, false);
+ exit;
+}
+
/**
* If we've got a tmp playlist then get the
* current song, and do any other crazyness
diff --git a/stream.php b/stream.php
index 9de680d0..4cd1e7a6 100644
--- a/stream.php
+++ b/stream.php
@@ -136,26 +136,26 @@ switch ($_REQUEST['action']) {
break;
case 'democratic':
// Don't let them loop it
+ // FIXME: This looks hacky
if (Config::get('play_type') == 'democratic') {
Config::set('play_type', 'stream', true);
}
default:
- if (Config::get('play_type') == 'stream') {
+ $stream_type = Config::get('play_type');
+ if ($stream_type == 'stream') {
$stream_type = Config::get('playlist_type');
}
- else {
- $stream_type = Config::get('play_type');
- }
break;
}
-/* Start the Stream */
debug_event('stream.php' , 'Stream Type: ' . $stream_type . ' Media IDs: '. json_encode($media_ids), 5);
-$stream = new Stream($stream_type, $media_ids);
+$playlist = new Stream_Playlist();
+$playlist->add($media_ids);
if (isset($urls)) {
- $stream->add_urls($urls);
+ $playlist->add_urls($urls);
}
-$stream->start();
+// Depending on the stream type, will either generate a redirect or actually do
+// the streaming.
+$playlist->generate_playlist($stream_type, true);
-} // end method switch
?>