Merge branch 'devel' of dev.sourcefabric.org:airtime into devel
Conflicts: airtime_mvc/application/models/Block.php
This commit is contained in:
commit
028e089c82
|
@ -126,7 +126,7 @@ class PlaylistController extends Zend_Controller_Action
|
|||
|
||||
private function blockDynamic($obj)
|
||||
{
|
||||
$this->view->error = "You cannot add tracks to dynamic block.";
|
||||
$this->view->error = "You cannot add tracks to dynamic blocks.";
|
||||
$this->createFullResponse($obj);
|
||||
}
|
||||
|
||||
|
@ -151,6 +151,11 @@ class PlaylistController extends Zend_Controller_Action
|
|||
Logging::log("{$e->getLine()}");
|
||||
Logging::log("{$e->getMessage()}");
|
||||
}
|
||||
|
||||
private function playlistDenied($obj) {
|
||||
$this->view->error = "You cannot add playlists to smart playlists.";
|
||||
$this->createFullResponse($obj);
|
||||
}
|
||||
|
||||
public function indexAction()
|
||||
{
|
||||
|
@ -306,9 +311,15 @@ class PlaylistController extends Zend_Controller_Action
|
|||
$obj->addAudioClips($ids, $afterItem, $addType);
|
||||
} else if ($obj->isStatic()) {
|
||||
// if the dest is a block object
|
||||
//check if any items are playlists
|
||||
foreach($ids as $id) {
|
||||
if (is_array($id) && isset($id[1]) && $id[1] == 'playlist') {
|
||||
throw new Exception('playlist to block');
|
||||
}
|
||||
}
|
||||
$obj->addAudioClips($ids, $afterItem, $addType);
|
||||
} else {
|
||||
throw new BlockDynamicException;
|
||||
throw new Exception('track to dynamic');
|
||||
}
|
||||
$this->createUpdateResponse($obj);
|
||||
}
|
||||
|
@ -318,11 +329,14 @@ class PlaylistController extends Zend_Controller_Action
|
|||
catch (PlaylistNotFoundException $e) {
|
||||
$this->playlistNotFound($obj_type);
|
||||
}
|
||||
catch (BlockDynamicException $e) {
|
||||
$this->blockDynamic($obj);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->playlistUnknownError($e);
|
||||
if ($e->getMessage() == 'playlist to block') {
|
||||
$this->playlistDenied($obj);
|
||||
} else if ($e->getMessage() == 'track to dynamic') {
|
||||
$this->blockDynamic($obj);
|
||||
} else {
|
||||
$this->playlistUnknownError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,9 +33,14 @@ class WebstreamController extends Zend_Controller_Action
|
|||
{
|
||||
$request = $this->getRequest();
|
||||
|
||||
Application_Model_Webstream::save($request);
|
||||
$analysis = Application_Model_Webstream::analyzeFormData($request);
|
||||
|
||||
$this->view->statusMessage = "<div class='success'>Webstream saved.</div>";
|
||||
|
||||
if (Application_Model_Webstream::isValid($analysis)) {
|
||||
Application_Model_Webstream::save($request);
|
||||
$this->view->statusMessage = "<div class='success'>Webstream saved.</div>";
|
||||
} else {
|
||||
$this->view->statusMessage = "<div class='errors'>Invalid form values.</div>";
|
||||
$this->view->analysis = $analysis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -408,9 +408,14 @@ EOT;
|
|||
|
||||
foreach ($p_items as $ac) {
|
||||
Logging::log("Adding audio file {$ac}");
|
||||
|
||||
$res = $this->insertBlockElement($this->buildEntry($ac, $pos));
|
||||
$pos = $pos + 1;
|
||||
|
||||
if (is_array($ac) && $ac[1] == 'audioclip') {
|
||||
$res = $this->insertBlockElement($this->buildEntry($ac[0], $pos));
|
||||
$pos = $pos + 1;
|
||||
} elseif (!is_array($ac)) {
|
||||
$res = $this->insertBlockElement($this->buildEntry($ac, $pos));
|
||||
$pos = $pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
//reset the positions of the remaining items.
|
||||
|
|
|
@ -75,20 +75,82 @@ class Application_Model_Webstream{
|
|||
return $leftOvers;
|
||||
|
||||
}
|
||||
/*
|
||||
Array
|
||||
(
|
||||
[controller] => Webstream
|
||||
[action] => save
|
||||
[module] => default
|
||||
[format] => json
|
||||
[description] => desc
|
||||
[url] => http://
|
||||
[length] => 00h 20m
|
||||
[name] => Default
|
||||
)
|
||||
*/
|
||||
|
||||
|
||||
public static function analyzeFormData($request)
|
||||
{
|
||||
$valid = array("length" => array(true, ''),
|
||||
"url" => array(true, ''));
|
||||
|
||||
$length = trim($request->getParam("length"));
|
||||
$result = preg_match("/^([0-9]{1,2})h ([0-5][0-9])m$/", $length, $matches);
|
||||
if (!$result == 1 || !count($matches) == 3) {
|
||||
$valid['length'][0] = false;
|
||||
$valid['length'][1] = 'Length should be of form "00h 00m"';
|
||||
}
|
||||
|
||||
|
||||
$url = trim($request->getParam("url"));
|
||||
//simple validator that checks to make sure that the url starts with http(s),
|
||||
//and that the domain is at least 1 letter long followed by a period.
|
||||
$result = preg_match("/^(http|https):\/\/.+\./", $url, $matches);
|
||||
|
||||
if ($result == 0) {
|
||||
$valid['url'][0] = false;
|
||||
$valid['url'][1] = 'URL should be of form "http://www.domain.com/mount"';
|
||||
}
|
||||
|
||||
|
||||
$name = trim($request->getParam("name"));
|
||||
if (strlen($name) == 0) {
|
||||
$valid['name'][0] = false;
|
||||
$valid['name'][1] = 'Webstream name cannot be empty';
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
public static function isValid($analysis)
|
||||
{
|
||||
foreach ($analysis as $k => $v) {
|
||||
if ($v[0] == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function save($request)
|
||||
{
|
||||
Logging::log($request->getParams());
|
||||
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
|
||||
Logging::log($userInfo);
|
||||
|
||||
$length = trim($request->getParam("length"));
|
||||
preg_match("/^([0-9]{1,2})h ([0-5][0-9])m$/", $length, $matches);
|
||||
$hours = $matches[1];
|
||||
$minutes = $matches[2];
|
||||
$di = new DateInterval("PT{$hours}H{$minutes}M");
|
||||
$dblength = $di->format("%H:%I");
|
||||
|
||||
$result = preg_match("/^([0-9]{1,2})h ([0-5][0-9])m$/", $length, $matches);
|
||||
if ($result == 1 && count($matches) == 3) {
|
||||
$hours = $matches[1];
|
||||
$minutes = $matches[2];
|
||||
$di = new DateInterval("PT{$hours}H{$minutes}M");
|
||||
$dblength = $di->format("%H:%I");
|
||||
} else {
|
||||
//This should never happen because we should have already validated
|
||||
//in the controller
|
||||
throw new Exception("Invalid date format: $length");
|
||||
}
|
||||
|
||||
#TODO: These should be validated by a Zend Form.
|
||||
$webstream = new CcWebstream();
|
||||
$webstream->setDbName($request->getParam("name"));
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<input id="pl_lastMod" type="hidden" value="<?php echo $this->ws->getLastModified('U'); ?>"></input>
|
||||
<div class="status" style="display:none;"></div>
|
||||
<div class="playlist_title">
|
||||
<div id="name-error" class="errors" style="display:none;"></div>
|
||||
<h3 id="ws_name">
|
||||
<a id="playlist_name_display" contenteditable="true"><?php echo $this->ws->getName(); ?></a>
|
||||
</h3>
|
||||
|
@ -22,10 +23,12 @@
|
|||
<textarea cols="80" rows="24" id="description" name="description"><?php echo $this->ws->getDescription(); ?></textarea>
|
||||
</dd>
|
||||
<dt id="submit-label" style="display: none;"> </dt>
|
||||
<div id="url-error" class="errors" style="display:none;"></div>
|
||||
<dt id="streamurl-label"><label for="streamurl">Stream URL:</label></dt>
|
||||
<dd id="streamurl-element">
|
||||
<input type="text" value="http://" size="40"/>
|
||||
</dd>
|
||||
<div id="length-error" class="errors" style="display:none;"></div>
|
||||
<dt id="streamlength-label"><label for="streamlength">Default Length:</label></dt>
|
||||
<dd id="streamlength-element">
|
||||
<input type="text" value="00h 20m"/>
|
||||
|
|
|
@ -128,11 +128,14 @@ var AIRTIME = (function(AIRTIME){
|
|||
for (i = 0, length = aData.length; i < length; i++) {
|
||||
temp = aData[i];
|
||||
if (temp.ftype === "audioclip" || temp.ftype === "block") {
|
||||
aMediaIds.push(temp.id);
|
||||
aMediaIds.push(new Array (temp.id, temp.ftype));
|
||||
}
|
||||
}
|
||||
|
||||
AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after');
|
||||
if (aMediaIds.length > 0) {
|
||||
AIRTIME.playlist.fnAddItems(aMediaIds, undefined, 'after');
|
||||
} else {
|
||||
alert('You cannot add playlists to smart playlists');
|
||||
}
|
||||
});
|
||||
|
||||
//delete from library.
|
||||
|
|
|
@ -642,7 +642,7 @@ var AIRTIME = (function(AIRTIME) {
|
|||
|
||||
//delete through the playlist controller, will reset
|
||||
//playlist screen if this is the currently edited playlist.
|
||||
if (data.ftype === "playlist" && screen === "playlist") {
|
||||
if ((data.ftype === "playlist" || data.ftype === "block") && screen === "playlist") {
|
||||
callback = function() {
|
||||
|
||||
if (confirm('Are you sure you want to delete the selected item?')) {
|
||||
|
|
|
@ -533,15 +533,30 @@ var AIRTIME = (function(AIRTIME){
|
|||
var streamurl = $pl.find("#streamurl-element input").val();
|
||||
var length = $pl.find("#streamlength-element input").val();
|
||||
var name = $pl.find("#playlist_name_display").text();
|
||||
|
||||
//hide any previous errors (if any)
|
||||
$("#side_playlist .errors").empty().hide();
|
||||
|
||||
var url = 'Webstream/save';
|
||||
$.post(url,
|
||||
{format: "json", description: description, url:streamurl, length: length, name: name},
|
||||
function(json){
|
||||
var $status = $("#side_playlist .status");
|
||||
$status.html(json.statusMessage);
|
||||
$status.show();
|
||||
setTimeout(function(){$status.fadeOut("slow", function(){$status.empty()})}, 5000);
|
||||
if (json.analysis){
|
||||
for (var s in json.analysis){
|
||||
var field = json.analysis[s];
|
||||
|
||||
if (!field[0]) {
|
||||
var elemId = "#"+s+"-error";
|
||||
var $div = $("#side_playlist " + elemId).text(field[1]).show();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var $status = $("#side_playlist .status");
|
||||
$status.html(json.statusMessage);
|
||||
$status.show();
|
||||
setTimeout(function(){$status.fadeOut("slow", function(){$status.empty()})}, 5000);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ class PypoPush(Thread):
|
|||
|
||||
self.pushed_objects = {}
|
||||
self.logger = logging.getLogger('push')
|
||||
self.current_stream_info = None
|
||||
|
||||
def main(self):
|
||||
loops = 0
|
||||
|
@ -73,38 +74,54 @@ class PypoPush(Thread):
|
|||
|
||||
tnow = datetime.utcnow()
|
||||
current_event_chain, original_chain = self.get_current_chain(chains, tnow)
|
||||
if len(current_event_chain) > 0 and len(liquidsoap_queue_approx) == 0:
|
||||
#Something is scheduled but Liquidsoap is not playing anything!
|
||||
#Need to schedule it immediately..this might happen if Liquidsoap crashed.
|
||||
|
||||
if len(current_event_chain) > 0:
|
||||
try:
|
||||
chains.remove(original_chain)
|
||||
except ValueError, e:
|
||||
self.logger.error(str(e))
|
||||
if len(liquidsoap_queue_approx) == 0 and current_event_chain[0]['type'] == 'file':
|
||||
#Something is scheduled but Liquidsoap is not playing anything!
|
||||
#Need to schedule it immediately..this might happen if Liquidsoap crashed.
|
||||
self.modify_cue_point(current_event_chain[0])
|
||||
next_media_item_chain = current_event_chain
|
||||
time_until_next_play = 0
|
||||
#sleep for 0.2 seconds to give pypo-file time to copy.
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
if not self.current_stream_info and current_event_chain[0]['type'] == 'stream':
|
||||
#a stream is schedule but Liquidsoap is not playing it. Need to start it.
|
||||
next_media_item_chain = current_event_chain
|
||||
time_until_next_play = 0
|
||||
continue
|
||||
|
||||
self.modify_cue_point(current_event_chain[0])
|
||||
next_media_item_chain = current_event_chain
|
||||
time_until_next_play = 0
|
||||
#sleep for 0.2 seconds to give pypo-file time to copy.
|
||||
time.sleep(0.2)
|
||||
#At this point we know that Liquidsoap is playing something, and that something
|
||||
#is scheduled. We need to verify whether the schedule we just received matches
|
||||
#what Liquidsoap is playing, and if not, correct it.
|
||||
media_chain = filter(lambda item: (item["type"] == "file"), current_event_chain)
|
||||
stream_chain = filter(lambda item: (item["type"] == "stream"), current_event_chain)
|
||||
self.handle_new_schedule(media_schedule, liquidsoap_queue_approx, media_chain, stream_chain)
|
||||
|
||||
|
||||
#At this point everything in the present has been taken care of and Liquidsoap
|
||||
#is playing whatever is scheduled.
|
||||
#Now we need to prepare ourselves for future scheduled events.
|
||||
#
|
||||
next_media_item_chain = self.get_next_schedule_chain(chains, tnow)
|
||||
|
||||
self.logger.debug("Next schedule chain: %s", next_media_item_chain)
|
||||
if next_media_item_chain is not None:
|
||||
try:
|
||||
chains.remove(next_media_item_chain)
|
||||
except ValueError, e:
|
||||
self.logger.error(str(e))
|
||||
|
||||
chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S")
|
||||
time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow())
|
||||
self.logger.debug("Blocking %s seconds until show start", time_until_next_play)
|
||||
else:
|
||||
media_chain = filter(lambda item: (item["type"] == "file"), current_event_chain)
|
||||
self.handle_new_media_schedule(media_schedule, liquidsoap_queue_approx, media_chain)
|
||||
|
||||
next_media_item_chain = self.get_next_schedule_chain(chains, tnow)
|
||||
|
||||
self.logger.debug("Next schedule chain: %s", next_media_item_chain)
|
||||
if next_media_item_chain is not None:
|
||||
try:
|
||||
chains.remove(next_media_item_chain)
|
||||
except ValueError, e:
|
||||
self.logger.error(str(e))
|
||||
|
||||
chain_start = datetime.strptime(next_media_item_chain[0]['start'], "%Y-%m-%d-%H-%M-%S")
|
||||
time_until_next_play = self.date_interval_to_seconds(chain_start - datetime.utcnow())
|
||||
self.logger.debug("Blocking %s seconds until show start", time_until_next_play)
|
||||
else:
|
||||
self.logger.debug("Blocking indefinitely since no show scheduled")
|
||||
time_until_next_play = None
|
||||
self.logger.debug("Blocking indefinitely since no show scheduled")
|
||||
time_until_next_play = None
|
||||
except Empty, e:
|
||||
#We only get here when a new chain of tracks are ready to be played.
|
||||
self.push_to_liquidsoap(next_media_item_chain)
|
||||
|
@ -166,7 +183,7 @@ class PypoPush(Thread):
|
|||
|
||||
return liquidsoap_queue_approx
|
||||
|
||||
def handle_new_media_schedule(self, media_schedule, liquidsoap_queue_approx, media_chain):
|
||||
def handle_new_schedule(self, media_schedule, liquidsoap_queue_approx, media_chain, stream_chain):
|
||||
"""
|
||||
This function's purpose is to gracefully handle situations where
|
||||
Liquidsoap already has a track in its queue, but the schedule
|
||||
|
@ -175,6 +192,18 @@ class PypoPush(Thread):
|
|||
queue.
|
||||
"""
|
||||
|
||||
if self.current_stream_info:
|
||||
if len(stream_chain) == 0:
|
||||
#Liquidsoap is rebroadcasting a webstream, but there is no stream
|
||||
#in the schedule. Let's stop streaming.
|
||||
self.stop_web_stream(self.current_stream_info)
|
||||
elif self.current_stream_info['uri'] != stream_chain[0]['uri']:
|
||||
#Liquidsoap is rebroadcasting a webstream and a webstream is scheduled
|
||||
#to play, but they are not the same!
|
||||
self.stop_web_stream(self.current_stream_info)
|
||||
self.start_web_stream(stream_chain[0])
|
||||
|
||||
|
||||
problem_at_iteration = self.find_removed_items(media_schedule, liquidsoap_queue_approx)
|
||||
|
||||
if problem_at_iteration is not None:
|
||||
|
@ -388,6 +417,7 @@ class PypoPush(Thread):
|
|||
tn.write("exit\n")
|
||||
self.logger.debug(tn.read_all())
|
||||
|
||||
self.current_stream_info = media_item
|
||||
except Exception, e:
|
||||
self.logger.error(str(e))
|
||||
finally:
|
||||
|
@ -406,6 +436,7 @@ class PypoPush(Thread):
|
|||
tn.write("exit\n")
|
||||
self.logger.debug(tn.read_all())
|
||||
|
||||
self.current_stream_info = None
|
||||
except Exception, e:
|
||||
self.logger.error(str(e))
|
||||
finally:
|
||||
|
|
Loading…
Reference in New Issue