Merge branch '2.0.x' into devel

Conflicts:
	VERSION
	airtime_mvc/application/models/Schedule.php
	airtime_mvc/application/models/Show.php
	airtime_mvc/public/js/airtime/dashboard/helperfunctions.js
	install_minimal/include/airtime-constants.php
	python_apps/api_clients/api_client.py
	python_apps/pypo/pypocli.py
	python_apps/pypo/pypofetch.py
This commit is contained in:
Martin Konecny 2012-03-12 17:52:17 -04:00
commit 6f270bfb3d
21 changed files with 1664 additions and 293 deletions

View File

@ -1,3 +1,10 @@
=======
CREDITS
=======
Version 2.0.2
-------------
Same as previous version.
=======
CREDITS
=======

View File

@ -3,24 +3,31 @@
class Application_Model_Nowplaying
{
private static function CreateHeaderRow($p_showName, $p_showStart, $p_showEnd){
return array("h", "", $p_showStart, $p_showEnd, $p_showName, "", "", "", "", "", "");
}
private static function CreateHeaderRow($p_showName, $p_showStart, $p_showEnd){
return array("h", "", $p_showStart, $p_showEnd, $p_showName, "", "", "", "", "", "");
}
private static function CreateDatatableRows($p_dbRows){
private static function CreateDatatableRows($p_dbRows){
$dataTablesRows = array();
$epochNow = time();
foreach ($p_dbRows as $dbRow){
$showStartDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($dbRow['show_starts']);
$showEndDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($dbRow['show_ends']);
$itemStartDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($dbRow['item_starts']);
$itemEndDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($dbRow['item_ends']);
$lastRow = end($p_dbRows);
//Information about show is true for all rows in parameter so only check the last row's show
//start and end times.
if (isset($lastRow)){
$showStartDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($lastRow['show_starts']);
$showEndDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($lastRow['show_ends']);
$showStarts = $showStartDateTime->format("Y-m-d H:i:s");
$showEnds = $showEndDateTime->format("Y-m-d H:i:s");
}
foreach ($p_dbRows as $dbRow) {
$itemStartDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($dbRow['item_starts']);
$itemEndDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($dbRow['item_ends']);
$itemStarts = $itemStartDateTime->format("Y-m-d H:i:s");
$itemEnds = $itemEndDateTime->format("Y-m-d H:i:s");
@ -44,54 +51,82 @@ class Application_Model_Nowplaying
$dbRow['playlist_name'], $dbRow['show_name'], $status);
}
return $dataTablesRows;
}
//Modify the last entry in the data table to adjust its end time to be equal to the
//shows end time if it exceeds it.
$lastRow = end($dataTablesRows);
if (isset($lastRow) && strtotime($showEnds) < strtotime($lastRow[3])){
$dataTablesRows[sizeof($dataTablesRows)-1][3] = $showEnds;
}
return $dataTablesRows;
}
private static function CreateGapRow($p_gapTime){
return array("g", "", "", "", $p_gapTime, "", "", "", "", "", "");
}
private static function CreateGapRow($p_gapTime){
return array("g", "", "", "", $p_gapTime, "", "", "", "", "", "");
}
private static function CreateRecordingRow($p_showInstance){
return array("r", "", "", "", $p_showInstance->getName(), "", "", "", "", "", "");
}
private static function CreateRecordingRow($p_showInstance){
return array("r", "", "", "", $p_showInstance->getName(), "", "", "", "", "", "");
}
public static function GetDataGridData($viewType, $dateString){
if ($viewType == "now"){
$dateTime = new DateTime("now", new DateTimeZone("UTC"));
$timeNow = $dateTime->format("Y-m-d H:i:s");
/*
* The purpose of this function is to return an array of scheduled
* items. There are two parameters. $p_viewType can be either "now"
* or "day". If "now", we show all scheduled items in the near future.
*
* If "day" we need to find what day was requested by the user, and return
* scheduled items for that day.
*
* $p_dateString is only used when $p_viewType is "day" it is in the format
* "2012-12-31". In this case it tells us which day to use.
*/
public static function GetDataGridData($p_viewType, $p_dateString){
$startCutoff = 60;
$endCutoff = 86400; //60*60*24 - seconds in a day
if ($p_viewType == "now"){
$start_dt = new DateTime("now", new DateTimeZone("UTC"));
$end_dt = clone $start_dt;
$start_dt->sub(new DateInterval("PT60S"));
$end_dt->add(new DateInterval("PT24H"));
} else {
$date = new Application_Model_DateHelper;
$time = $date->getTime();
$date->setDate($dateString." ".$time);
$timeNow = $date->getUtcTimestamp();
//convert to UTC
$utc_dt = Application_Model_DateHelper::ConvertToUtcDateTime($p_dateString);
$start_dt = $utc_dt;
$end_dt = clone $utc_dt;
$end_dt->add(new DateInterval("PT24H"));
}
$starts = $start_dt->format("Y-m-d H:i:s");
$ends = $end_dt->format("Y-m-d H:i:s");
$startCutoff = $date->getNowDayStartDiff();
$endCutoff = $date->getNowDayEndDiff();
$showIds = Application_Model_ShowInstance::GetShowsInstancesIdsInRange($starts, $ends);
//get all the pieces to be played between the start cut off and the end cut off.
$scheduledItems = Application_Model_Schedule::getScheduleItemsInRange($starts, $ends);
$orderedScheduledItems;
foreach ($scheduledItems as $scheduledItem){
$orderedScheduledItems[$scheduledItem['instance_id']][] = $scheduledItem;
}
$data = array();
$showIds = Application_Model_ShowInstance::GetShowsInstancesIdsInRange($timeNow, $startCutoff, $endCutoff);
foreach ($showIds as $showId){
$instanceId = $showId['id'];
//gets the show information
$si = new Application_Model_ShowInstance($instanceId);
$showId = $si->getShowId();
$show = new Application_Model_Show($showId);
$showStartDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($si->getShowInstanceStart());
$showEndDateTime = Application_Model_DateHelper::ConvertToLocalDateTime($si->getShowInstanceEnd());
//append show header row
$data[] = self::CreateHeaderRow($show->getName(), $showStartDateTime->format("Y-m-d H:i:s"), $showEndDateTime->format("Y-m-d H:i:s"));
$scheduledItems = $si->getScheduleItemsInRange($timeNow, $startCutoff, $endCutoff);
$dataTablesRows = self::CreateDatatableRows($scheduledItems);
$dataTablesRows = self::CreateDatatableRows($orderedScheduledItems[$instanceId]);
//append show audio item rows
$data = array_merge($data, $dataTablesRows);
@ -99,11 +134,12 @@ class Application_Model_Nowplaying
//append show gap time row
$gapTime = self::FormatDuration($si->getShowEndGapTime(), true);
if ($si->isRecorded())
$data[] = self::CreateRecordingRow($si);
$data[] = self::CreateRecordingRow($si);
else if ($gapTime > 0)
$data[] = self::CreateGapRow($gapTime);
$data[] = self::CreateGapRow($gapTime);
}
$timeNow = gmdate("Y-m-d H:i:s");
$rows = Application_Model_Show::GetCurrentShow($timeNow);
Application_Model_Show::ConvertToLocalTimeZone($rows, array("starts", "ends", "start_timestamp", "end_timestamp"));
return array("currentShow"=>$rows, "rows"=>$data);

View File

@ -65,18 +65,76 @@ class Application_Model_Schedule {
$date = new Application_Model_DateHelper;
$timeNow = $date->getTimestamp();
$utcTimeNow = $date->getUtcTimestamp();
$currentShow = Application_Model_Show::GetCurrentShow($utcTimeNow);
$results = Application_Model_Schedule::GetPrevCurrentNext($utcTimeNow);
$range = array("env"=>APPLICATION_ENV,
"schedulerTime"=>$timeNow,
"previous"=>Application_Model_Dashboard::GetPreviousItem($utcTimeNow),
"current"=>Application_Model_Dashboard::GetCurrentItem($utcTimeNow),
"next"=>Application_Model_Dashboard::GetNextItem($utcTimeNow),
"currentShow"=>Application_Model_Show::GetCurrentShow($utcTimeNow),
"nextShow"=>Application_Model_Show::GetNextShows($utcTimeNow, 1),
"previous"=>$results['previous'],
"current"=>$results['current'],
"next"=>$results['next'],
"currentShow"=>$currentShow,
"nextShow"=>"",//Application_Model_Show::GetNextShows($utcTimeNow, 1),
"timezone"=> date("T"),
"timezoneOffset"=> date("Z"));
return $range;
}
/**
* Queries the database for the set of schedules one hour before and after the given time.
* If a show starts and ends within that time that is considered the current show. Then the
* scheduled item before it is the previous show, and the scheduled item after it is the next
* show. This way the dashboard getCurrentPlaylist is very fast. But if any one of the three
* show types are not found through this mechanism a call is made to the old way of querying
* the database to find the track info.
**/
public static function GetPrevCurrentNext($p_timeNow)
{
global $CC_CONFIG, $CC_DBC;
$sql = "Select ft.artist_name, ft.track_title, st.starts as starts, st.ends as ends, st.media_item_played as media_item_played
FROM cc_schedule st LEFT JOIN cc_files ft ON st.file_id = ft.id LEFT JOIN cc_show_instances sit ON st.instance_id = sit.id
WHERE st.starts > (TIMESTAMP '$p_timeNow'-INTERVAL '24 hours') AND st.starts < (TIMESTAMP '$p_timeNow'+INTERVAL '24 hours') AND st.starts < sit.ends
ORDER BY st.starts";
$row = $CC_DBC->GetAll($sql);
$numberOfRows = count($row);
$results;
$timeNowAsMillis = strtotime($p_timeNow);
for( $i = 0; $i < $numberOfRows && !isset($results); ++$i ){
if ((strtotime($row[$i]['starts']) <= $timeNowAsMillis) && (strtotime($row[$i]['ends']) >= $timeNowAsMillis)){
if ( $i - 1 >= 0){
$results['previous'] = array("name"=>$row[$i-1]["artist_name"]." - ".$row[$i-1]["track_title"],
"starts"=>$row[$i-1]["starts"],
"ends"=>$row[$i-1]["ends"]);
}
$results['current'] = array("name"=>$row[$i]["artist_name"]." - ".$row[$i]["track_title"],
"starts"=>$row[$i]["starts"],
"ends"=>$row[$i]["ends"],
"media_item_played"=>$row[$i]["media_item_played"],
"record"=>0);
if ( isset($row[$i+1])){
$results['next'] = array("name"=>$row[$i+1]["artist_name"]." - ".$row[$i+1]["track_title"],
"starts"=>$row[$i+1]["starts"],
"ends"=>$row[$i+1]["ends"]);
}
break;
}
}
if (!isset($results['previous'])){
$results['previous'] = Application_Model_Dashboard::GetPreviousItem($p_timeNow);
}
if (!isset($results['current'])){
$results['current'] = Application_Model_Dashboard::GetCurrentItem($p_timeNow);
}
if (!isset($results['next'])) {
$results['next'] = Application_Model_Dashboard::GetNextItem($p_timeNow);
}
return $results;
}
public static function GetLastScheduleItem($p_timeNow){
global $CC_CONFIG, $CC_DBC;
@ -195,23 +253,59 @@ class Application_Model_Schedule {
." WHERE st.starts < si.ends";
if ($timePeriod < 0){
$sql .= " AND st.ends < TIMESTAMP '$timeStamp'"
." AND st.ends > (TIMESTAMP '$timeStamp' - INTERVAL '$interval')"
." ORDER BY st.starts DESC"
." LIMIT $count";
} else if ($timePeriod == 0){
$sql .= " AND st.starts <= TIMESTAMP '$timeStamp'"
." AND st.ends >= TIMESTAMP '$timeStamp'";
} else if ($timePeriod > 0){
$sql .= " AND st.starts > TIMESTAMP '$timeStamp'"
." AND st.starts < (TIMESTAMP '$timeStamp' + INTERVAL '$interval')"
." ORDER BY st.starts"
." LIMIT $count";
}
$sql .= " AND st.ends < TIMESTAMP '$timeStamp'"
." AND st.ends > (TIMESTAMP '$timeStamp' - INTERVAL '$interval')"
." ORDER BY st.starts DESC"
." LIMIT $count";
} else if ($timePeriod == 0){
$sql .= " AND st.starts <= TIMESTAMP '$timeStamp'"
." AND st.ends >= TIMESTAMP '$timeStamp'";
} else if ($timePeriod > 0){
$sql .= " AND st.starts > TIMESTAMP '$timeStamp'"
." AND st.starts < (TIMESTAMP '$timeStamp' + INTERVAL '$interval')"
." ORDER BY st.starts"
." LIMIT $count";
}
$rows = $CC_DBC->GetAll($sql);
return $rows;
}
}
public static function getScheduleItemsInRange($starts, $ends)
{
global $CC_DBC, $CC_CONFIG;
$sql = "SELECT"
." si.starts as show_starts,"
." si.ends as show_ends,"
." si.rebroadcast as rebroadcast,"
." st.starts as item_starts,"
." st.ends as item_ends,"
." st.clip_length as clip_length,"
." ft.track_title as track_title,"
." ft.artist_name as artist_name,"
." ft.album_title as album_title,"
." s.name as show_name,"
." si.id as instance_id,"
." pt.name as playlist_name"
." FROM ".$CC_CONFIG["showInstances"]." si"
." LEFT JOIN ".$CC_CONFIG["scheduleTable"]." st"
." ON st.instance_id = si.id"
." LEFT JOIN ".$CC_CONFIG["playListTable"]." pt"
." ON st.playlist_id = pt.id"
." LEFT JOIN ".$CC_CONFIG["filesTable"]." ft"
." ON st.file_id = ft.id"
." LEFT JOIN ".$CC_CONFIG["showTable"]." s"
." ON si.show_id = s.id"
." WHERE ((si.starts < TIMESTAMP '$starts' AND si.ends > TIMESTAMP '$starts')"
." OR (si.starts > TIMESTAMP '$starts' AND si.ends < TIMESTAMP '$ends')"
." OR (si.starts < TIMESTAMP '$ends' AND si.ends > TIMESTAMP '$ends'))"
." AND (st.starts < si.ends)"
." ORDER BY si.id, si.starts, st.starts";
return $CC_DBC->GetAll($sql);
}
/*
*
@ -295,7 +389,7 @@ class Application_Model_Schedule {
}
public static function getSchduledPlaylistCount(){
global $CC_CONFIG, $CC_DBC;
global $CC_CONFIG, $CC_DBC;
$sql = "SELECT count(*) as cnt FROM ".$CC_CONFIG['scheduleTable'];
return $CC_DBC->GetOne($sql);
}
@ -635,16 +729,16 @@ class Application_Model_Schedule {
$isSaas = Application_Model_Preference::GetPlanLevel() == 'disabled'?false:true;
$formWhat = new Application_Form_AddShowWhat();
$formWho = new Application_Form_AddShowWho();
$formWhen = new Application_Form_AddShowWhen();
$formRepeats = new Application_Form_AddShowRepeats();
$formStyle = new Application_Form_AddShowStyle();
$formWho = new Application_Form_AddShowWho();
$formWhen = new Application_Form_AddShowWhen();
$formRepeats = new Application_Form_AddShowRepeats();
$formStyle = new Application_Form_AddShowStyle();
$formWhat->removeDecorator('DtDdWrapper');
$formWho->removeDecorator('DtDdWrapper');
$formWhen->removeDecorator('DtDdWrapper');
$formRepeats->removeDecorator('DtDdWrapper');
$formStyle->removeDecorator('DtDdWrapper');
$formWhat->removeDecorator('DtDdWrapper');
$formWho->removeDecorator('DtDdWrapper');
$formWhen->removeDecorator('DtDdWrapper');
$formRepeats->removeDecorator('DtDdWrapper');
$formStyle->removeDecorator('DtDdWrapper');
$p_view->what = $formWhat;
$p_view->when = $formWhen;
@ -655,11 +749,11 @@ class Application_Model_Schedule {
$formWhat->populate(array('add_show_id' => '-1'));
$formWhen->populate(array('add_show_start_date' => date("Y-m-d"),
'add_show_start_time' => '00:00',
'add_show_end_date_no_repeate' => date("Y-m-d"),
'add_show_end_time' => '01:00',
'add_show_end_date_no_repeate' => date("Y-m-d"),
'add_show_end_time' => '01:00',
'add_show_duration' => '1h'));
$formRepeats->populate(array('add_show_end_date' => date("Y-m-d")));
$formRepeats->populate(array('add_show_end_date' => date("Y-m-d")));
if(!$isSaas){
$formRecord = new Application_Form_AddShowRR();
@ -677,4 +771,3 @@ class Application_Model_Schedule {
$p_view->addNewShow = true;
}
}

View File

@ -239,22 +239,22 @@ class Application_Model_Show {
}
foreach($showDays as $showDay) {
Logging::log("Local show day is: {$showDay->getDbDay()}");
Logging::log("Local show day is: {$showDay->getDbDay()}");
Logging::log("First show day is: {$showDay->getDbFirstShow()}");
Logging::log("Id show days is: {$showDay->getDbId()}");
if (in_array($showDay->getDbDay(), $p_uncheckedDays)) {
$showDay->reload();
//Logging::log("Local show day is: {$showDay->getDbDay()}");
//Logging::log("First show day is: {$showDay->getDbFirstShow()}");
//Logging::log("Id show days is: {$showDay->getDbId()}");
$startDay = new DateTime("{$showDay->getDbFirstShow()} {$showDay->getDbStartTime()}", new DateTimeZone($showDay->getDbTimezone()));
Logging::log("Show start day: {$startDay->format('Y-m-d H:i:s')}");
if (in_array($showDay->getDbDay(), $p_uncheckedDays)) {
$showDay->reload();
//Logging::log("Local show day is: {$showDay->getDbDay()}");
//Logging::log("First show day is: {$showDay->getDbFirstShow()}");
//Logging::log("Id show days is: {$showDay->getDbId()}");
$startDay = new DateTime("{$showDay->getDbFirstShow()} {$showDay->getDbStartTime()}", new DateTimeZone($showDay->getDbTimezone()));
Logging::log("Show start day: {$startDay->format('Y-m-d H:i:s')}");
$startDay->setTimezone(new DateTimeZone("UTC"));
Logging::log("Show start day UTC: {$startDay->format('Y-m-d H:i:s')}");
$daysRemovedUTC[] = $startDay->format('w');
Logging::log("UTC show day is: {$startDay->format('w')}");
}
}
}
$uncheckedDaysImploded = implode(",", $daysRemovedUTC);
@ -867,18 +867,18 @@ class Application_Model_Show {
}
}
if ($p_data['add_show_start_date'] != $this->getStartDate()
|| $p_data['add_show_start_time'] != $this->getStartTime()){
//start date/time has changed
if ($p_data['add_show_start_date'] != $this->getStartDate()
|| $p_data['add_show_start_time'] != $this->getStartTime()){
//start date/time has changed
$newDate = strtotime($p_data['add_show_start_date']);
$oldDate = strtotime($this->getStartDate());
if ($newDate > $oldDate){
$this->removeAllInstancesBeforeDate($p_data['add_show_start_date']);
}
$newDate = strtotime($p_data['add_show_start_date']);
$oldDate = strtotime($this->getStartDate());
if ($newDate > $oldDate){
$this->removeAllInstancesBeforeDate($p_data['add_show_start_date']);
}
$this->updateStartDateTime($p_data, $p_endDate);
}
$this->updateStartDateTime($p_data, $p_endDate);
}
}
//Check if end date for the repeat option has changed. If so, need to take care
@ -1449,7 +1449,7 @@ class Application_Model_Show {
$sql = "SELECT starts, ends, record, rebroadcast, instance_id, show_id, name,
color, background_color, file_id, cc_show_instances.id AS instance_id,
last_scheduled
last_scheduled, time_filled
FROM cc_show_instances
LEFT JOIN cc_show ON cc_show.id = cc_show_instances.show_id
WHERE cc_show_instances.modified_instance = FALSE";
@ -1541,14 +1541,15 @@ class Application_Model_Show {
* -in UTC time
* @param boolean $editable
*/
public static function getFullCalendarEvents($start, $end, $editable=false)
public static function getFullCalendarEvents($p_start, $p_end, $p_editable=false)
{
$events = array();
$interval = $start->diff($end);
$days = $interval->format('%a');
$interval = $p_start->diff($p_end);
$days = $interval->format('%a');
$shows = Application_Model_Show::getShows($start, $end);
$shows = Application_Model_Show::getShows($p_start, $p_end);
$today_timestamp = gmdate("Y-m-d H:i:s");
@ -1556,20 +1557,32 @@ class Application_Model_Show {
$options = array();
//only bother calculating percent for week or day view.
if(intval($days) <= 7) {
$show_instance = new Application_Model_ShowInstance($show["instance_id"]);
$options["percent"] = $show_instance->getPercentScheduled();
if(intval($days) <= 7) {
$options["percent"] = Application_Model_Show::getPercentScheduled($show["starts"], $show["ends"], $show["time_filled"]);
}
if ($editable && (strtotime($today_timestamp) < strtotime($show["starts"]))) {
if ($p_editable && (strtotime($today_timestamp) < strtotime($show["starts"]))) {
$options["editable"] = true;
}
$events[] = Application_Model_Show::makeFullCalendarEvent($show, $options);
}
return $events;
}
/**
* Calculates the percentage of a show scheduled given the start and end times in date/time format
* and the time_filled as the total time the schow is scheduled for in time format.
**/
private static function getPercentScheduled($p_starts, $p_ends, $p_time_filled){
$durationSeconds = (strtotime($p_ends) - strtotime($p_starts));
$time_filled = Application_Model_Schedule::WallTimeToMillisecs($p_time_filled) / 1000;
$percent = ceil(( $time_filled / $durationSeconds) * 100);
return $percent;
}
private static function makeFullCalendarEvent($show, $options=array())
{

View File

@ -677,20 +677,22 @@ class Application_Model_ShowInstance {
return $results;
}
public static function GetShowsInstancesIdsInRange($p_timeNow, $p_start, $p_end)
public static function GetShowsInstancesIdsInRange($p_start, $p_end)
{
global $CC_DBC;
$sql = "SELECT id FROM cc_show_instances AS si "
."WHERE modified_instance != TRUE AND ("
."(si.starts < TIMESTAMP '$p_timeNow' - INTERVAL '$p_start seconds' "
."AND si.ends > TIMESTAMP '$p_timeNow' - INTERVAL '$p_start seconds') "
."OR (si.starts > TIMESTAMP '$p_timeNow' - INTERVAL '$p_start seconds' "
."AND si.ends < TIMESTAMP '$p_timeNow' + INTERVAL '$p_end seconds') "
."OR (si.starts < TIMESTAMP '$p_timeNow' + INTERVAL '$p_end seconds' "
."AND si.ends > TIMESTAMP '$p_timeNow' + INTERVAL '$p_end seconds') "
."(si.starts < TIMESTAMP '$p_start'"
."AND si.ends > TIMESTAMP '$p_start') "
."OR (si.starts > TIMESTAMP '$p_start' "
."AND si.ends < TIMESTAMP '$p_end') "
."OR (si.starts < TIMESTAMP '$p_end' "
."AND si.ends > TIMESTAMP '$p_end') "
.") "
." ORDER BY si.starts";
Logging::debug($sql);
$rows = $CC_DBC->GetAll($sql);
return $rows;
@ -732,7 +734,7 @@ class Application_Model_ShowInstance {
return $CC_DBC->GetAll($sql);
}
public function getLastAudioItemEnd(){
global $CC_DBC;

View File

@ -20,27 +20,26 @@ class Airtime_View_Helper_VersionNotify extends Zend_View_Helper_Abstract{
$current = Application_Model_Preference::GetAirtimeVersion();
$latest = Application_Model_Preference::GetLatestVersion();
$link = Application_Model_Preference::GetLatestLink();
$pattern = "/^([0-9]+)\.([0-9]+)\.[0-9]+/";
preg_match($pattern, $current, $curMatch);
preg_match($pattern, $latest, $latestMatch);
if(count($curMatch) == 0 || count($latestMatch) == 0) {
$currentExploded = explode('.', $current);
$latestExploded = explode('.', $latest);
if(count($currentExploded) != 3 || count($latestExploded) != 3) {
return "";
}
// Calculate major version diff;
// Example: if current = 1.9.5 and latest = 3.0.0, major diff = 11
// Calculate the version difference;
// Example: if current = 1.9.5 and latest = 3.0.0, diff = 105
// Note: algorithm assumes the number after 1st dot never goes above 9
$diff = (intval($latestMatch[1]) * 10 + intval($latestMatch[2]))
- (intval($curMatch[1]) * 10 + intval($curMatch[2]));
$versionDifference = (intval($latestExploded[0]) * 100 + intval($latestExploded[1]) *10 + intval($latestExploded[2]))
- (intval($currentExploded[0]) * 100 + intval($currentExploded[1] *10 + intval($currentExploded[2])));
// Pick icon
if(($diff == 0 && $current == $latest) || $diff < 0) {
// current version is up to date
// Pick icon based on distance this version is to the latest version available
if($versionDifference <= 0) {
// current version is up to date or newer
$class = "uptodate";
} else if($diff <= 2) {
} else if($versionDifference < 20) {
// 2 or less major versions back
$class = "update";
} else if($diff == 3) {
} else if($versionDifference < 30) {
// 3 major versions back
$class = "update2";
} else {
@ -48,7 +47,7 @@ class Airtime_View_Helper_VersionNotify extends Zend_View_Helper_Abstract{
$class = "outdated";
}
$result = "<div id='version-diff' style='display:none'>" . $diff . "</div>"
$result = "<div id='version-diff' style='display:none'>" . $versionDifference . "</div>"
. "<div id='version-current' style='display:none'>" . $current . "</div>"
. "<div id='version-latest' style='display:none'>" . $latest . "</div>"
. "<div id='version-link' style='display:none'>" . $link . "</div>"

View File

@ -12,3 +12,14 @@ RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
RewriteBase /
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json

View File

@ -0,0 +1,415 @@
/*
* jPlayer Plugin for jQuery JavaScript Library
* http://www.happyworm.com/jquery/jplayer
*
* Copyright (c) 2009 - 2011 Happyworm Ltd
* Dual licensed under the MIT and GPL licenses.
* - http://www.opensource.org/licenses/mit-license.php
* - http://www.gnu.org/copyleft/gpl.html
*
* Author: Mark J Panaghiston
* Version: 2.1.0
* Date: 1st September 2011
*
* FlashVars expected: (AS3 property of: loaderInfo.parameters)
* id: (URL Encoded: String) Id of jPlayer instance
* vol: (Number) Sets the initial volume
* muted: (Boolean in a String) Sets the initial muted state
* jQuery: (URL Encoded: String) Sets the jQuery var name. Used with: someVar = jQuery.noConflict(true);
*
* Compiled using: Adobe Flex Compiler (mxmlc) Version 4.5.1 build 21328
*/
package {
import flash.system.Security;
import flash.external.ExternalInterface;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.events.KeyboardEvent;
import flash.display.Sprite;
import happyworm.jPlayer.*;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import flash.events.ContextMenuEvent;
import flash.net.URLRequest;
import flash.net.navigateToURL;
public class Jplayer extends Sprite {
private var jQuery:String;
private var sentNumberFractionDigits:uint = 2;
public var commonStatus:JplayerStatus = new JplayerStatus(); // Used for inital ready event so volume is correct.
private var myInitTimer:Timer = new Timer(100, 0);
private var myMp3Player:JplayerMp3;
private var myMp4Player:JplayerMp4;
private var isMp3:Boolean = false;
private var isVideo:Boolean = false;
private var txLog:TextField;
private var debug:Boolean = false; // Set debug to false for release compile!
public function Jplayer() {
flash.system.Security.allowDomain("*");
jQuery = loaderInfo.parameters.jQuery + "('#" + loaderInfo.parameters.id + "').jPlayer";
commonStatus.volume = Number(loaderInfo.parameters.vol);
commonStatus.muted = loaderInfo.parameters.muted == "true";
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.addEventListener(Event.RESIZE, resizeHandler);
stage.addEventListener(MouseEvent.CLICK, clickHandler);
var initialVolume:Number = commonStatus.volume;
if(commonStatus.muted) {
initialVolume = 0;
}
myMp3Player = new JplayerMp3(initialVolume);
addChild(myMp3Player);
myMp4Player = new JplayerMp4(initialVolume);
addChild(myMp4Player);
setupListeners(!isMp3, isMp3); // Set up the listeners to the default isMp3 state.
// The ContextMenu only partially works. The menu select events never occur.
// Investigated and it is something to do with the way jPlayer inserts the Flash on the page.
// A simple test inserting the Jplayer.swf on a page using: 1) SWFObject 2.2 works. 2) AC_FL_RunContent() works.
// jPlayer Flash insertion is based on SWFObject 2.2 and the resaon behind this failure is not clear. The Flash insertion HTML on the page looks similar.
var myContextMenu:ContextMenu = new ContextMenu();
myContextMenu.hideBuiltInItems();
var menuItem_jPlayer:ContextMenuItem = new ContextMenuItem("jPlayer " + JplayerStatus.VERSION);
var menuItem_happyworm:ContextMenuItem = new ContextMenuItem("© 2009-2011 Happyworm Ltd", true);
menuItem_jPlayer.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuSelectHandler_jPlayer);
menuItem_happyworm.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, menuSelectHandler_happyworm);
myContextMenu.customItems.push(menuItem_jPlayer, menuItem_happyworm);
contextMenu = myContextMenu;
// Log console for dev compile option: debug
if(debug) {
txLog = new TextField();
txLog.x = 5;
txLog.y = 5;
txLog.width = 540;
txLog.height = 390;
txLog.border = true;
txLog.background = true;
txLog.backgroundColor = 0xEEEEFF;
txLog.multiline = true;
txLog.text = "jPlayer " + JplayerStatus.VERSION;
txLog.visible = false;
this.addChild(txLog);
this.stage.addEventListener(KeyboardEvent.KEY_UP, keyboardHandler);
myMp3Player.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler);
myMp4Player.addEventListener(JplayerEvent.DEBUG_MSG, debugMsgHandler);
}
// Delay init() because Firefox 3.5.7+ developed a bug with local testing in Firebug.
myInitTimer.addEventListener(TimerEvent.TIMER, init);
myInitTimer.start();
}
private function init(e:TimerEvent):void {
myInitTimer.stop();
if(ExternalInterface.available) {
ExternalInterface.addCallback("fl_setAudio_mp3", fl_setAudio_mp3);
ExternalInterface.addCallback("fl_setAudio_m4a", fl_setAudio_m4a);
ExternalInterface.addCallback("fl_setVideo_m4v", fl_setVideo_m4v);
ExternalInterface.addCallback("fl_clearMedia", fl_clearMedia);
ExternalInterface.addCallback("fl_load", fl_load);
ExternalInterface.addCallback("fl_play", fl_play);
ExternalInterface.addCallback("fl_pause", fl_pause);
ExternalInterface.addCallback("fl_play_head", fl_play_head);
ExternalInterface.addCallback("fl_volume", fl_volume);
ExternalInterface.addCallback("fl_mute", fl_mute);
ExternalInterface.call(jQuery, "jPlayerFlashEvent", JplayerEvent.JPLAYER_READY, extractStatusData(commonStatus)); // See JplayerStatus() class for version number.
}
}
private function setupListeners(oldMP3:Boolean, newMP3:Boolean):void {
if(oldMP3 != newMP3) {
if(newMP3) {
listenToMp3(true);
listenToMp4(false);
} else {
listenToMp3(false);
listenToMp4(true);
}
}
}
private function listenToMp3(active:Boolean):void {
if(active) {
myMp3Player.addEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
myMp3Player.addEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
} else {
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
myMp3Player.removeEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
}
}
private function listenToMp4(active:Boolean):void {
if(active) {
myMp4Player.addEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
myMp4Player.addEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerMetaDataHandler); // Note the unique handler
} else {
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_ERROR, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_PROGRESS, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_TIMEUPDATE, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_ENDED, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_PLAY, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_PAUSE, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_LOADSTART, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_SEEKING, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_SEEKED, jPlayerFlashEvent);
myMp4Player.removeEventListener(JplayerEvent.JPLAYER_LOADEDMETADATA, jPlayerMetaDataHandler); // Note the unique handler
}
}
private function fl_setAudio_mp3(src:String):Boolean {
if (src != null) {
log("fl_setAudio_mp3: "+src);
setupListeners(isMp3, true);
isMp3 = true;
isVideo = false;
myMp4Player.clearFile();
myMp3Player.setFile(src);
return true;
} else {
log("fl_setAudio_mp3: null");
return false;
}
}
private function fl_setAudio_m4a(src:String):Boolean {
if (src != null) {
log("fl_setAudio_m4a: "+src);
setupListeners(isMp3, false);
isMp3 = false;
isVideo = false;
myMp3Player.clearFile();
myMp4Player.setFile(src);
return true;
} else {
log("fl_setAudio_m4a: null");
return false;
}
}
private function fl_setVideo_m4v(src:String):Boolean {
if (src != null) {
log("fl_setVideo_m4v: "+src);
setupListeners(isMp3, false);
isMp3 = false;
isVideo = true;
myMp3Player.clearFile();
myMp4Player.setFile(src);
return true;
} else {
log("fl_setVideo_m4v: null");
return false;
}
}
private function fl_clearMedia():void {
log("clearMedia.");
myMp3Player.clearFile();
myMp4Player.clearFile();
}
private function fl_load():Boolean {
log("load.");
if(isMp3) {
return myMp3Player.load();
} else {
return myMp4Player.load();
}
}
private function fl_play(time:Number = NaN):Boolean {
log("play: time = " + time);
if(isMp3) {
return myMp3Player.play(time * 1000); // Flash uses milliseconds
} else {
return myMp4Player.play(time * 1000); // Flash uses milliseconds
}
}
private function fl_pause(time:Number = NaN):Boolean {
log("pause: time = " + time);
if(isMp3) {
return myMp3Player.pause(time * 1000); // Flash uses milliseconds
} else {
return myMp4Player.pause(time * 1000); // Flash uses milliseconds
}
}
private function fl_play_head(percent:Number):Boolean {
log("play_head: "+percent+"%");
if(isMp3) {
return myMp3Player.playHead(percent);
} else {
return myMp4Player.playHead(percent);
}
}
private function fl_volume(v:Number):void {
log("volume: "+v);
commonStatus.volume = v;
if(!commonStatus.muted) {
myMp3Player.setVolume(v);
myMp4Player.setVolume(v);
}
}
private function fl_mute(mute:Boolean):void {
log("mute: "+mute);
commonStatus.muted = mute;
if(mute) {
myMp3Player.setVolume(0);
myMp4Player.setVolume(0);
} else {
myMp3Player.setVolume(commonStatus.volume);
myMp4Player.setVolume(commonStatus.volume);
}
}
private function jPlayerFlashEvent(e:JplayerEvent):void {
log("jPlayer Flash Event: " + e.type + ": " + e.target);
if(ExternalInterface.available) {
ExternalInterface.call(jQuery, "jPlayerFlashEvent", e.type, extractStatusData(e.data));
}
}
private function extractStatusData(data:JplayerStatus):Object {
var myStatus:Object = {
version: JplayerStatus.VERSION,
src: data.src,
paused: !data.isPlaying, // Changing this name requires inverting all assignments and conditional statements.
srcSet: data.srcSet,
seekPercent: data.seekPercent,
currentPercentRelative: data.currentPercentRelative,
currentPercentAbsolute: data.currentPercentAbsolute,
currentTime: data.currentTime / 1000, // JavaScript uses seconds
duration: data.duration / 1000, // JavaScript uses seconds
volume: commonStatus.volume,
muted: commonStatus.muted
};
log("extractStatusData: sp="+myStatus.seekPercent+" cpr="+myStatus.currentPercentRelative+" cpa="+myStatus.currentPercentAbsolute+" ct="+myStatus.currentTime+" d="+myStatus.duration);
return myStatus;
}
private function jPlayerMetaDataHandler(e:JplayerEvent):void {
log("jPlayerMetaDataHandler:" + e.target);
if(ExternalInterface.available) {
resizeHandler(new Event(Event.RESIZE));
ExternalInterface.call(jQuery, "jPlayerFlashEvent", e.type, extractStatusData(e.data));
}
}
private function resizeHandler(e:Event):void {
log("resizeHandler: stageWidth = " + stage.stageWidth + " | stageHeight = " + stage.stageHeight);
var mediaX:Number = 0;
var mediaY:Number = 0;
var mediaWidth:Number = 0;
var mediaHeight:Number = 0;
if(stage.stageWidth > 0 && stage.stageHeight > 0 && myMp4Player.myVideo.width > 0 && myMp4Player.myVideo.height > 0) {
var aspectRatioStage:Number = stage.stageWidth / stage.stageHeight;
var aspectRatioVideo:Number = myMp4Player.myVideo.width / myMp4Player.myVideo.height;
if(aspectRatioStage < aspectRatioVideo) {
mediaWidth = stage.stageWidth;
mediaHeight = stage.stageWidth / aspectRatioVideo;
mediaX = 0;
mediaY = (stage.stageHeight - mediaHeight) / 2;
} else {
mediaWidth = stage.stageHeight * aspectRatioVideo;
mediaHeight = stage.stageHeight;
mediaX = (stage.stageWidth - mediaWidth) / 2;
mediaY = 0;
}
resizeEntity(myMp4Player, mediaX, mediaY, mediaWidth, mediaHeight);
}
if(debug && stage.stageWidth > 20 && stage.stageHeight > 20) {
txLog.width = stage.stageWidth - 10;
txLog.height = stage.stageHeight - 10;
}
}
private function resizeEntity(entity:Sprite, mediaX:Number, mediaY:Number, mediaWidth:Number, mediaHeight:Number):void {
entity.x = mediaX;
entity.y = mediaY;
entity.width = mediaWidth;
entity.height = mediaHeight;
}
private function clickHandler(e:MouseEvent):void {
if(isMp3) {
jPlayerFlashEvent(new JplayerEvent(JplayerEvent.JPLAYER_CLICK, myMp3Player.myStatus, "click"))
} else {
jPlayerFlashEvent(new JplayerEvent(JplayerEvent.JPLAYER_CLICK, myMp4Player.myStatus, "click"))
}
}
// This event is never called. See comments in class constructor.
private function menuSelectHandler_jPlayer(e:ContextMenuEvent):void {
navigateToURL(new URLRequest("http://jplayer.org/"), "_blank");
}
// This event is never called. See comments in class constructor.
private function menuSelectHandler_happyworm(e:ContextMenuEvent):void {
navigateToURL(new URLRequest("http://happyworm.com/"), "_blank");
}
private function log(t:String):void {
if(debug) {
txLog.text = t + "\n" + txLog.text;
}
}
private function debugMsgHandler(e:JplayerEvent):void {
log(e.msg);
}
private function keyboardHandler(e:KeyboardEvent):void {
log("keyboardHandler: e.keyCode = " + e.keyCode);
switch(e.keyCode) {
case 68 : // d
txLog.visible = !txLog.visible;
log("Toggled log display: " + txLog.visible);
break;
case 76 : // l
if(e.ctrlKey && e.shiftKey) {
txLog.text = "Cleared log.";
}
break;
}
}
}
}

Binary file not shown.

View File

@ -0,0 +1,69 @@
/*
* jPlayer Plugin for jQuery JavaScript Library
* http://www.happyworm.com/jquery/jplayer
*
* Copyright (c) 2009 - 2011 Happyworm Ltd
* Dual licensed under the MIT and GPL licenses.
* - http://www.opensource.org/licenses/mit-license.php
* - http://www.gnu.org/copyleft/gpl.html
*
* Author: Mark J Panaghiston
* Date: 8th August 2011
*/
package happyworm.jPlayer {
import flash.events.Event;
public class JplayerEvent extends Event {
// The event strings must match those in the JavaScript's $.jPlayer.event object
public static const JPLAYER_READY:String = "jPlayer_ready";
public static const JPLAYER_FLASHRESET:String = "jPlayer_flashreset"; // Handled in JavaScript
public static const JPLAYER_RESIZE:String = "jPlayer_resize"; // Handled in JavaScript
public static const JPLAYER_REPEAT:String = "jPlayer_repeat"; // Handled in JavaScript
public static const JPLAYER_CLICK:String = "jPlayer_click";
public static const JPLAYER_ERROR:String = "jPlayer_error";
public static const JPLAYER_WARNING:String = "jPlayer_warning"; // Currently not used by the flash solution
public static const JPLAYER_LOADSTART:String = "jPlayer_loadstart";
public static const JPLAYER_PROGRESS:String = "jPlayer_progress";
public static const JPLAYER_SUSPEND:String = "jPlayer_suspend"; // Not implemented
public static const JPLAYER_ABORT:String = "jPlayer_abort"; // Not implemented
public static const JPLAYER_EMPTIED:String = "jPlayer_emptied"; // Not implemented
public static const JPLAYER_STALLED:String = "jPlayer_stalled"; // Not implemented
public static const JPLAYER_PLAY:String = "jPlayer_play";
public static const JPLAYER_PAUSE:String = "jPlayer_pause";
public static const JPLAYER_LOADEDMETADATA:String = "jPlayer_loadedmetadata"; // MP3 has no equivilent
public static const JPLAYER_LOADEDDATA:String = "jPlayer_loadeddata"; // Not implemented
public static const JPLAYER_WAITING:String = "jPlayer_waiting"; // Not implemented
public static const JPLAYER_PLAYING:String = "jPlayer_playing"; // Not implemented
public static const JPLAYER_CANPLAY:String = "jPlayer_canplay"; // Not implemented
public static const JPLAYER_CANPLAYTHROUGH:String = "jPlayer_canplaythrough"; // Not implemented
public static const JPLAYER_SEEKING:String = "jPlayer_seeking";
public static const JPLAYER_SEEKED:String = "jPlayer_seeked";
public static const JPLAYER_TIMEUPDATE:String = "jPlayer_timeupdate";
public static const JPLAYER_ENDED:String = "jPlayer_ended";
public static const JPLAYER_RATECHANGE:String = "jPlayer_ratechange"; // Not implemented
public static const JPLAYER_DURATIONCHANGE:String = "jPlayer_durationchange"; // Not implemented
public static const JPLAYER_VOLUMECHANGE:String = "jPlayer_volumechange"; // See JavaScript
// Events used internal to jPlayer's Flash.
public static const DEBUG_MSG:String = "debug_msg";
public var data:JplayerStatus;
public var msg:String = ""
public function JplayerEvent(type:String, data:JplayerStatus, msg:String = "", bubbles:Boolean = false, cancelable:Boolean = false) {
super(type, bubbles, cancelable);
this.data = data;
this.msg = msg;
}
public override function clone():Event {
return new JplayerEvent(type, data, msg, bubbles, cancelable);
}
public override function toString():String {
return formatToString("JplayerEvent", "type", "bubbles", "cancelable", "eventPhase", "data", "msg");
}
}
}

View File

@ -0,0 +1,328 @@
/*
* jPlayer Plugin for jQuery JavaScript Library
* http://www.happyworm.com/jquery/jplayer
*
* Copyright (c) 2009 - 2011 Happyworm Ltd
* Dual licensed under the MIT and GPL licenses.
* - http://www.opensource.org/licenses/mit-license.php
* - http://www.gnu.org/copyleft/gpl.html
*
* Author: Mark J Panaghiston
* Date: 1st September 2011
*/
package happyworm.jPlayer {
import flash.display.Sprite;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundLoaderContext;
import flash.media.SoundTransform;
import flash.net.URLRequest;
import flash.utils.Timer;
import flash.errors.IOError;
import flash.events.*;
public class JplayerMp3 extends Sprite {
private var mySound:Sound = new Sound();
private var myChannel:SoundChannel = new SoundChannel();
private var myContext:SoundLoaderContext = new SoundLoaderContext(3000, false);
private var myTransform:SoundTransform = new SoundTransform();
private var myRequest:URLRequest = new URLRequest();
private var timeUpdateTimer:Timer = new Timer(250, 0); // Matched to HTML event freq
private var progressTimer:Timer = new Timer(250, 0); // Matched to HTML event freq
private var seekingTimer:Timer = new Timer(100, 0); // Internal: How often seeking is checked to see if it is over.
public var myStatus:JplayerStatus = new JplayerStatus();
public function JplayerMp3(volume:Number) {
timeUpdateTimer.addEventListener(TimerEvent.TIMER, timeUpdateHandler);
progressTimer.addEventListener(TimerEvent.TIMER, progressHandler);
seekingTimer.addEventListener(TimerEvent.TIMER, seekingHandler);
setVolume(volume);
}
public function setFile(src:String):void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "setFile: " + src));
if(myStatus.isPlaying) {
myChannel.stop();
progressUpdates(false);
timeUpdates(false);
}
try {
mySound.close();
} catch (err:IOError) {
// Occurs if the file is either yet to be opened or has finished downloading.
}
mySound = null;
mySound = new Sound();
mySound.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
mySound.addEventListener(Event.OPEN, loadOpen);
mySound.addEventListener(Event.COMPLETE, loadComplete);
myRequest = new URLRequest(src);
myStatus.reset();
myStatus.src = src;
myStatus.srcSet = true;
timeUpdateEvent();
}
public function clearFile():void {
setFile("");
myStatus.srcSet = false;
}
private function errorHandler(err:IOErrorEvent):void {
// MP3 player needs to stop progress and timeupdate events as they are started before the error occurs.
// NB: The MP4 player works differently and the error occurs before they are started.
progressUpdates(false);
timeUpdates(false);
myStatus.error(); // Resets status except the src, and it sets srcError property.
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ERROR, myStatus));
}
private function loadOpen(e:Event):void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "loadOpen:"));
myStatus.loading();
if(myStatus.playOnLoad) {
myStatus.playOnLoad = false; // Capture the flag
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADSTART, myStatus)); // So loadstart event happens before play event occurs.
play();
} else {
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADSTART, myStatus));
pause();
}
progressUpdates(true);
}
private function loadComplete(e:Event):void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "loadComplete:"));
myStatus.loaded();
progressUpdates(false);
progressEvent();
}
private function soundCompleteHandler(e:Event):void {
myStatus.pausePosition = 0;
myStatus.isPlaying = false;
timeUpdates(false);
timeUpdateEvent();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ENDED, myStatus));
}
private function progressUpdates(active:Boolean):void {
// Using a timer rather than Flash's load progress event, because that event gave data at about 200Hz. The 10Hz timer is closer to HTML5 norm.
if(active) {
progressTimer.start();
} else {
progressTimer.stop();
}
}
private function progressHandler(e:TimerEvent):void {
progressEvent();
}
private function progressEvent():void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "progressEvent:"));
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PROGRESS, myStatus));
}
private function timeUpdates(active:Boolean):void {
if(active) {
timeUpdateTimer.start();
} else {
timeUpdateTimer.stop();
}
}
private function timeUpdateHandler(e:TimerEvent):void {
timeUpdateEvent();
}
private function timeUpdateEvent():void {
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_TIMEUPDATE, myStatus));
}
private function seeking(active:Boolean):void {
if(active) {
if(!myStatus.isSeeking) {
seekingEvent();
seekingTimer.start();
}
} else {
seekingTimer.stop();
}
}
private function seekingHandler(e:TimerEvent):void {
if(myStatus.pausePosition <= getDuration()) {
seekedEvent();
seeking(false);
if(myStatus.playOnSeek) {
myStatus.playOnSeek = false; // Capture the flag.
play();
}
} else if(myStatus.isLoaded && (myStatus.pausePosition > getDuration())) {
// Illegal seek time
seeking(false);
seekedEvent();
pause(0);
}
}
private function seekingEvent():void {
myStatus.isSeeking = true;
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKING, myStatus));
}
private function seekedEvent():void {
myStatus.isSeeking = false;
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKED, myStatus));
}
public function load():Boolean {
if(myStatus.loadRequired()) {
myStatus.startingDownload();
mySound.load(myRequest, myContext);
return true;
} else {
return false;
}
}
public function play(time:Number = NaN):Boolean {
var wasPlaying:Boolean = myStatus.isPlaying;
if(!isNaN(time) && myStatus.srcSet) {
if(myStatus.isPlaying) {
myChannel.stop();
myStatus.isPlaying = false;
}
myStatus.pausePosition = time;
}
if(myStatus.isStartingDownload) {
myStatus.playOnLoad = true; // Raise flag, captured in loadOpen()
return true;
} else if(myStatus.loadRequired()) {
myStatus.playOnLoad = true; // Raise flag, captured in loadOpen()
return load();
} else if((myStatus.isLoading || myStatus.isLoaded) && !myStatus.isPlaying) {
if(myStatus.isLoaded && myStatus.pausePosition > getDuration()) { // The time is invalid, ie., past the end.
myStatus.pausePosition = 0;
timeUpdates(false);
timeUpdateEvent();
if(wasPlaying) { // For when playing and then get a play(huge)
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE, myStatus));
}
} else if(myStatus.pausePosition > getDuration()) {
myStatus.playOnSeek = true;
seeking(true);
} else {
myStatus.isPlaying = true; // Set immediately before playing. Could affects events.
myChannel = mySound.play(myStatus.pausePosition);
myChannel.soundTransform = myTransform;
myChannel.addEventListener(Event.SOUND_COMPLETE, soundCompleteHandler);
timeUpdates(true);
if(!wasPlaying) {
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PLAY, myStatus));
}
}
return true;
} else {
return false;
}
}
public function pause(time:Number = NaN):Boolean {
myStatus.playOnLoad = false; // Reset flag in case load/play issued immediately before this command, ie., before loadOpen() event.
myStatus.playOnSeek = false; // Reset flag in case play(time) issued before the command and is still seeking to time set.
var wasPlaying:Boolean = myStatus.isPlaying;
// To avoid possible loops with timeupdate and pause(time). A pause() does not have the problem.
var alreadyPausedAtTime:Boolean = false;
if(!isNaN(time) && myStatus.pausePosition == time) {
alreadyPausedAtTime = true;
}
if(myStatus.isPlaying) {
myStatus.isPlaying = false;
myChannel.stop();
if(myChannel.position > 0) { // Required otherwise a fast play then pause causes myChannel.position to equal zero and not the correct value. ie., When it happens leave pausePosition alone.
myStatus.pausePosition = myChannel.position;
}
}
if(!isNaN(time) && myStatus.srcSet) {
myStatus.pausePosition = time;
}
if(wasPlaying) {
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE, myStatus));
}
if(myStatus.isStartingDownload) {
return true;
} else if(myStatus.loadRequired()) {
if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation.
return load();
} else {
return true; // Technically the pause(0) succeeded. ie., It did nothing, since nothing was required.
}
} else if(myStatus.isLoading || myStatus.isLoaded) {
if(myStatus.isLoaded && myStatus.pausePosition > getDuration()) { // The time is invalid, ie., past the end.
myStatus.pausePosition = 0;
} else if(myStatus.pausePosition > getDuration()) {
seeking(true);
}
timeUpdates(false);
// Need to be careful with timeupdate event, otherwise a pause in a timeupdate event can cause a loop.
// Neither pause() nor pause(time) will cause a timeupdate loop.
if(wasPlaying || !isNaN(time) && !alreadyPausedAtTime) {
timeUpdateEvent();
}
return true;
} else {
return false;
}
}
public function playHead(percent:Number):Boolean {
var time:Number = percent * getDuration() / 100;
if(myStatus.isPlaying || myStatus.playOnLoad || myStatus.playOnSeek) {
return play(time);
} else {
return pause(time);
}
}
public function setVolume(v:Number):void {
myStatus.volume = v;
myTransform.volume = v;
myChannel.soundTransform = myTransform;
}
private function updateStatusValues():void {
myStatus.seekPercent = 100 * getLoadRatio();
myStatus.currentTime = getCurrentTime();
myStatus.currentPercentRelative = 100 * getCurrentRatioRel();
myStatus.currentPercentAbsolute = 100 * getCurrentRatioAbs();
myStatus.duration = getDuration();
}
public function getLoadRatio():Number {
if((myStatus.isLoading || myStatus.isLoaded) && mySound.bytesTotal > 0) {
return mySound.bytesLoaded / mySound.bytesTotal;
} else {
return 0;
}
}
public function getDuration():Number {
if(mySound.length > 0) {
return mySound.length;
} else {
return 0;
}
}
public function getCurrentTime():Number {
if(myStatus.isPlaying) {
return myChannel.position;
} else {
return myStatus.pausePosition;
}
}
public function getCurrentRatioRel():Number {
if((getDuration() > 0) && (getCurrentTime() <= getDuration())) {
return getCurrentTime() / getDuration();
} else {
return 0;
}
}
public function getCurrentRatioAbs():Number {
return getCurrentRatioRel() * getLoadRatio();
}
}
}

View File

@ -0,0 +1,413 @@
/*
* jPlayer Plugin for jQuery JavaScript Library
* http://www.happyworm.com/jquery/jplayer
*
* Copyright (c) 2009 - 2011 Happyworm Ltd
* Dual licensed under the MIT and GPL licenses.
* - http://www.opensource.org/licenses/mit-license.php
* - http://www.gnu.org/copyleft/gpl.html
*
* Author: Mark J Panaghiston
* Date: 7th August 2011
*/
package happyworm.jPlayer {
import flash.display.Sprite;
import flash.media.Video;
import flash.media.SoundTransform;
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.utils.Timer;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
public class JplayerMp4 extends Sprite {
public var myVideo:Video = new Video();
private var myConnection:NetConnection;
private var myStream:NetStream;
private var myTransform:SoundTransform = new SoundTransform();
public var myStatus:JplayerStatus = new JplayerStatus();
private var timeUpdateTimer:Timer = new Timer(250, 0); // Matched to HTML event freq
private var progressTimer:Timer = new Timer(250, 0); // Matched to HTML event freq
private var seekingTimer:Timer = new Timer(100, 0); // Internal: How often seeking is checked to see if it is over.
public function JplayerMp4(volume:Number) {
myConnection = new NetConnection();
myConnection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
myConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
myVideo.smoothing = true;
this.addChild(myVideo);
timeUpdateTimer.addEventListener(TimerEvent.TIMER, timeUpdateHandler);
progressTimer.addEventListener(TimerEvent.TIMER, progressHandler);
seekingTimer.addEventListener(TimerEvent.TIMER, seekingHandler);
myStatus.volume = volume;
}
private function progressUpdates(active:Boolean):void {
if(active) {
progressTimer.start();
} else {
progressTimer.stop();
}
}
private function progressHandler(e:TimerEvent):void {
if(myStatus.isLoading) {
if(getLoadRatio() == 1) { // Close as can get to a loadComplete event since client.onPlayStatus only works with FMS
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "progressHandler: loadComplete"));
myStatus.loaded();
progressUpdates(false);
}
}
progressEvent();
}
private function progressEvent():void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "progressEvent:"));
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PROGRESS, myStatus));
}
private function timeUpdates(active:Boolean):void {
if(active) {
timeUpdateTimer.start();
} else {
timeUpdateTimer.stop();
}
}
private function timeUpdateHandler(e:TimerEvent):void {
timeUpdateEvent();
}
private function timeUpdateEvent():void {
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_TIMEUPDATE, myStatus));
}
private function seeking(active:Boolean):void {
if(active) {
if(!myStatus.isSeeking) {
seekingEvent();
}
seekingTimer.start();
} else {
if(myStatus.isSeeking) {
seekedEvent();
}
seekingTimer.stop();
}
}
private function seekingHandler(e:TimerEvent):void {
if(getSeekTimeRatio() <= getLoadRatio()) {
seeking(false);
if(myStatus.playOnSeek) {
myStatus.playOnSeek = false; // Capture the flag.
play(myStatus.pausePosition); // Must pass time or the seek time is never set.
} else {
pause(myStatus.pausePosition); // Must pass time or the stream.time is read.
}
} else if(myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration) {
// Illegal seek time
seeking(false);
pause(0);
}
}
private function seekingEvent():void {
myStatus.isSeeking = true;
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKING, myStatus));
}
private function seekedEvent():void {
myStatus.isSeeking = false;
updateStatusValues();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_SEEKED, myStatus));
}
private function netStatusHandler(e:NetStatusEvent):void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "netStatusHandler: '" + e.info.code + "'"));
switch(e.info.code) {
case "NetConnection.Connect.Success":
connectStream();
break;
case "NetStream.Play.Start":
// This event code occurs once, when the media is opened. Equiv to loadOpen() in mp3 player.
myStatus.loading();
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADSTART, myStatus));
progressUpdates(true);
// See onMetaDataHandler() for other condition, since duration is vital.
break;
case "NetStream.Play.Stop":
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "NetStream.Play.Stop: getDuration() - getCurrentTime() = " + (getDuration() - getCurrentTime())));
// Check if media is at the end (or close) otherwise this was due to download bandwidth stopping playback. ie., Download is not fast enough.
if(Math.abs(getDuration() - getCurrentTime()) < 150) { // Testing found 150ms worked best for M4A files, where playHead(99.9) caused a stuck state due to firing with ~116ms left to play.
endedEvent();
}
break;
case "NetStream.Seek.InvalidTime":
// Used for capturing invalid set times and clicks on the end of the progress bar.
endedEvent();
break;
case "NetStream.Play.StreamNotFound":
myStatus.error(); // Resets status except the src, and it sets srcError property.
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ERROR, myStatus));
break;
}
// "NetStream.Seek.Notify" event code is not very useful. It occurs after every seek(t) command issued and does not appear to wait for the media to be ready.
}
private function endedEvent():void {
var wasPlaying:Boolean = myStatus.isPlaying;
pause(0);
timeUpdates(false);
timeUpdateEvent();
if(wasPlaying) {
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_ENDED, myStatus));
}
}
private function securityErrorHandler(event:SecurityErrorEvent):void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "securityErrorHandler."));
}
private function connectStream():void {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "connectStream."));
var customClient:Object = new Object();
customClient.onMetaData = onMetaDataHandler;
// customClient.onPlayStatus = onPlayStatusHandler; // According to the forums and my tests, onPlayStatus only works with FMS (Flash Media Server).
myStream = null;
myStream = new NetStream(myConnection);
myStream.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
myStream.client = customClient;
myVideo.attachNetStream(myStream);
setVolume(myStatus.volume);
myStream.play(myStatus.src);
}
public function setFile(src:String):void {
if(myStream != null) {
myStream.close();
}
myVideo.clear();
progressUpdates(false);
timeUpdates(false);
myStatus.reset();
myStatus.src = src;
myStatus.srcSet = true;
timeUpdateEvent();
}
public function clearFile():void {
setFile("");
myStatus.srcSet = false;
}
public function load():Boolean {
if(myStatus.loadRequired()) {
myStatus.startingDownload();
myConnection.connect(null);
return true;
} else {
return false;
}
}
public function play(time:Number = NaN):Boolean {
var wasPlaying:Boolean = myStatus.isPlaying;
if(!isNaN(time) && myStatus.srcSet) {
if(myStatus.isPlaying) {
myStream.pause();
myStatus.isPlaying = false;
}
myStatus.pausePosition = time;
}
if(myStatus.isStartingDownload) {
myStatus.playOnLoad = true; // Raise flag, captured in onMetaDataHandler()
return true;
} else if(myStatus.loadRequired()) {
myStatus.playOnLoad = true; // Raise flag, captured in onMetaDataHandler()
return load();
} else if((myStatus.isLoading || myStatus.isLoaded) && !myStatus.isPlaying) {
if(myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration) { // The time is invalid, ie., past the end.
myStream.pause(); // Since it is playing by default at this point.
myStatus.pausePosition = 0;
myStream.seek(0);
timeUpdates(false);
timeUpdateEvent();
if(wasPlaying) { // For when playing and then get a play(huge)
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE, myStatus));
}
} else if(getSeekTimeRatio() > getLoadRatio()) { // Use an estimate based on the downloaded amount
myStatus.playOnSeek = true;
seeking(true);
myStream.pause(); // Since it is playing by default at this point.
} else {
if(!isNaN(time)) { // Avoid using seek() when it is already correct.
myStream.seek(myStatus.pausePosition/1000); // Since time is in ms and seek() takes seconds
}
myStatus.isPlaying = true; // Set immediately before playing. Could affects events.
myStream.resume();
timeUpdates(true);
if(!wasPlaying) {
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PLAY, myStatus));
}
}
return true;
} else {
return false;
}
}
public function pause(time:Number = NaN):Boolean {
myStatus.playOnLoad = false; // Reset flag in case load/play issued immediately before this command, ie., before onMetadata() event.
myStatus.playOnSeek = false; // Reset flag in case play(time) issued before the command and is still seeking to time set.
var wasPlaying:Boolean = myStatus.isPlaying;
// To avoid possible loops with timeupdate and pause(time). A pause() does not have the problem.
var alreadyPausedAtTime:Boolean = false;
if(!isNaN(time) && myStatus.pausePosition == time) {
alreadyPausedAtTime = true;
}
// Need to wait for metadata to load before ever issuing a pause. The metadata handler will call this function if needed, when ready.
if(myStream != null && myStatus.metaDataReady) { // myStream is a null until the 1st media is loaded. ie., The 1st ever setMedia being followed by a pause() or pause(t).
myStream.pause();
}
if(myStatus.isPlaying) {
myStatus.isPlaying = false;
myStatus.pausePosition = myStream.time * 1000;
}
if(!isNaN(time) && myStatus.srcSet) {
myStatus.pausePosition = time;
}
if(wasPlaying) {
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_PAUSE, myStatus));
}
if(myStatus.isStartingDownload) {
return true;
} else if(myStatus.loadRequired()) {
if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation.
return load();
} else {
return true; // Technically the pause(0) succeeded. ie., It did nothing, since nothing was required.
}
} else if(myStatus.isLoading || myStatus.isLoaded) {
if(myStatus.metaDataReady && myStatus.pausePosition > myStatus.duration) { // The time is invalid, ie., past the end.
myStatus.pausePosition = 0;
myStream.seek(0);
seekedEvent(); // Deals with seeking effect when using setMedia() then pause(huge). NB: There is no preceeding seeking event.
} else if(!isNaN(time)) {
if(getSeekTimeRatio() > getLoadRatio()) { // Use an estimate based on the downloaded amount
seeking(true);
} else {
if(myStatus.metaDataReady) { // Otherwise seek(0) will stop the metadata loading.
myStream.seek(myStatus.pausePosition/1000);
}
}
}
timeUpdates(false);
// Need to be careful with timeupdate event, otherwise a pause in a timeupdate event can cause a loop.
// Neither pause() nor pause(time) will cause a timeupdate loop.
if(wasPlaying || !isNaN(time) && !alreadyPausedAtTime) {
timeUpdateEvent();
}
return true;
} else {
return false;
}
}
public function playHead(percent:Number):Boolean {
var time:Number = percent * getDuration() * getLoadRatio() / 100;
if(myStatus.isPlaying || myStatus.playOnLoad || myStatus.playOnSeek) {
return play(time);
} else {
return pause(time);
}
}
public function setVolume(v:Number):void {
myStatus.volume = v;
myTransform.volume = v;
if(myStream != null) {
myStream.soundTransform = myTransform;
}
}
private function updateStatusValues():void {
myStatus.seekPercent = 100 * getLoadRatio();
myStatus.currentTime = getCurrentTime();
myStatus.currentPercentRelative = 100 * getCurrentRatioRel();
myStatus.currentPercentAbsolute = 100 * getCurrentRatioAbs();
myStatus.duration = getDuration();
}
public function getLoadRatio():Number {
if((myStatus.isLoading || myStatus.isLoaded) && myStream.bytesTotal > 0) {
return myStream.bytesLoaded / myStream.bytesTotal;
} else {
return 0;
}
}
public function getDuration():Number {
return myStatus.duration; // Set from meta data.
}
public function getCurrentTime():Number {
if(myStatus.isPlaying) {
return myStream.time * 1000;
} else {
return myStatus.pausePosition;
}
}
public function getCurrentRatioRel():Number {
if((getLoadRatio() > 0) && (getCurrentRatioAbs() <= getLoadRatio())) {
return getCurrentRatioAbs() / getLoadRatio();
} else {
return 0;
}
}
public function getCurrentRatioAbs():Number {
if(getDuration() > 0) {
return getCurrentTime() / getDuration();
} else {
return 0;
}
}
public function getSeekTimeRatio():Number {
if(getDuration() > 0) {
return myStatus.pausePosition / getDuration();
} else {
return 1;
}
}
public function onMetaDataHandler(info:Object):void { // Used in connectStream() in myStream.client object.
// This event occurs when jumping to the start of static files! ie., seek(0) will cause this event to occur.
if(!myStatus.metaDataReady) {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "onMetaDataHandler: " + info.duration + " | " + info.width + "x" + info.height));
myStatus.metaDataReady = true; // Set flag so that this event only effects jPlayer the 1st time.
myStatus.metaData = info;
myStatus.duration = info.duration * 1000; // Only available via Meta Data.
if(info.width != undefined) {
myVideo.width = info.width;
}
if(info.height != undefined) {
myVideo.height = info.height;
}
if(myStatus.playOnLoad) {
myStatus.playOnLoad = false; // Capture the flag
if(myStatus.pausePosition > 0 ) { // Important for setMedia followed by play(time).
play(myStatus.pausePosition);
} else {
play(); // Not always sending pausePosition avoids the extra seek(0) for a normal play() command.
}
} else {
pause(myStatus.pausePosition); // Always send the pausePosition. Important for setMedia() followed by pause(time). Deals with not reading stream.time with setMedia() and play() immediately followed by stop() or pause(0)
}
this.dispatchEvent(new JplayerEvent(JplayerEvent.JPLAYER_LOADEDMETADATA, myStatus));
} else {
this.dispatchEvent(new JplayerEvent(JplayerEvent.DEBUG_MSG, myStatus, "onMetaDataHandler: Already read (NO EFFECT)"));
}
}
}
}

View File

@ -0,0 +1,101 @@
/*
* jPlayer Plugin for jQuery JavaScript Library
* http://www.happyworm.com/jquery/jplayer
*
* Copyright (c) 2009 - 2011 Happyworm Ltd
* Dual licensed under the MIT and GPL licenses.
* - http://www.opensource.org/licenses/mit-license.php
* - http://www.gnu.org/copyleft/gpl.html
*
* Author: Mark J Panaghiston
* Date: 1st September 2011
*/
package happyworm.jPlayer {
public class JplayerStatus {
public static const VERSION:String = "2.1.0"; // The version of the Flash jPlayer entity.
public var volume:Number = 0.5; // Not affected by reset()
public var muted:Boolean = false; // Not affected by reset()
public var src:String;
public var srcError:Boolean;
public var srcSet:Boolean;
public var isPlaying:Boolean;
public var isSeeking:Boolean;
public var playOnLoad:Boolean;
public var playOnSeek:Boolean;
public var isStartingDownload:Boolean;
public var isLoading:Boolean;
public var isLoaded:Boolean;
public var pausePosition:Number;
public var seekPercent:Number;
public var currentTime:Number;
public var currentPercentRelative:Number;
public var currentPercentAbsolute:Number;
public var duration:Number;
public var metaDataReady:Boolean;
public var metaData:Object;
public function JplayerStatus() {
reset();
}
public function reset():void {
src = "";
srcError = false;
srcSet = false;
isPlaying = false;
isSeeking = false;
playOnLoad = false;
playOnSeek = false;
isStartingDownload = false;
isLoading = false;
isLoaded = false;
pausePosition = 0;
seekPercent = 0;
currentTime = 0;
currentPercentRelative = 0;
currentPercentAbsolute = 0;
duration = 0;
metaDataReady = false;
metaData = {};
}
public function error():void {
var srcSaved:String = src;
reset();
src = srcSaved;
srcError = true;
}
public function loadRequired():Boolean {
return (srcSet && !isStartingDownload && !isLoading && !isLoaded);
}
public function startingDownload():void {
isStartingDownload = true;
isLoading = false;
isLoaded = false;
}
public function loading():void {
isStartingDownload = false;
isLoading = true;
isLoaded = false;
}
public function loaded():void {
isStartingDownload = false;
isLoading = false;
isLoaded = true;
}
}
}

View File

@ -1,3 +1,13 @@
2.0.2 - February 28, 2012
* Bug Fixes:
* Fixed Airtime could stop automatically playing after 24 hours if the web interface isn't used (regression in 2.0.1).
* Fixed Airtime could stop automatically recording after 2 hours if the web interface isn't used.
* Fixed upgrading from 1.8.2 when the stor directory was a symlink would cause filenames to not be preserved.
* Fixed Day View in the Now Playing tab showed some items on incorrect days.
* Fixed problems with having an equal '=' sign as an icecast password
* Other
* Various optimizations to make Airtime feel snappier in the browser. Various views should
load much quicker.
2.0.1 - February 14, 2012
* Changes:
* Widgets should have a version string so users can make sure widgets are up to date

View File

@ -617,7 +617,18 @@ class Airtime190Upgrade{
echo "Save DbMd to File".PHP_EOL;
$stor_dir = realpath($values['general']['base_files_dir']."/stor");
/* Do not use realpath. It expands a symlinked path into its real path
* which then causes us problems for string comparisons later on. */
//$stor_dir = realpath($values['general']['base_files_dir']."/stor");
$baseDir = $values['general']['base_files_dir'];
if ($baseDir[strlen($baseDir)-1] != '/'){
$baseDir.='/';
}
$stor_dir = $baseDir.'stor';
$files = CcFilesQuery::create()
->setFormatter(ModelCriteria::FORMAT_ON_DEMAND)

View File

@ -81,12 +81,25 @@ try:
print " Found %s (%s) on %s architecture" % (fullname, codename, arch)
print " * Installing Liquidsoap binary"
if (os.path.exists("%s/liquidsoap_%s_%s"%(PATH_LIQUIDSOAP_BIN, codename, arch))):
shutil.copy("%s/liquidsoap_%s_%s"%(PATH_LIQUIDSOAP_BIN, codename, arch), "%s/liquidsoap"%PATH_LIQUIDSOAP_BIN)
else:
print "Unsupported system architecture."
sys.exit(1)
binary_path = os.path.join(PATH_LIQUIDSOAP_BIN, "liquidsoap_%s_%s" % (codename, arch))
try:
shutil.copy(binary_path, "%s/liquidsoap"%PATH_LIQUIDSOAP_BIN)
except IOError, e:
"""
shutil.copy can throw this exception for two reasons. First reason is that it cannot open the source file.
This is when the liquidsoap file we requested does not exist, and therefore tells the user we don't support
their OS/System architecture. The second reason for this exception is the shutil.copy cannot open the target file.
Since this script is being run as root (and we cannot install to a read-only device), this should never happen. So
it is safe to assume this exception is a result of the first case.
Note: We cannot simply use os.path.exists before this, since it sometimes gives us "false" incorrectly
"""
print "Unsupported OS/system architecture."
sys.exit(1)
#generate liquidsoap config file
#access the DB and generate liquidsoap.cfg under /etc/airtime/
ac = api_client.api_client_factory(config)

View File

@ -15,6 +15,7 @@ from threading import Thread
from subprocess import Popen, PIPE
from datetime import datetime
from datetime import timedelta
from Queue import Empty
import filecmp
from api_clients import api_client
@ -478,6 +479,19 @@ class PypoFetch(Thread):
while True:
self.logger.info("Loop #%s", loops)
try:
"""
our simple_queue.get() requires a timeout, in which case we
fetch the Airtime schedule manually. It is important to fetch
the schedule periodically because if we didn't, we would only
get schedule updates via RabbitMq if the user was constantly
using the Airtime interface.
If the user is not using the interface, RabbitMq messages are not
sent, and we will have very stale (or non-existent!) data about the
schedule.
Currently we are checking every 3600 seconds (1 hour)
"""
message = self.fetch_queue.get(block=True, timeout=3600)
self.handle_message(message)
except Exception, e:

View File

@ -1,26 +1,5 @@
#!/bin/bash
#-------------------------------------------------------------------------------
# Copyright (c) 2010 Sourcefabric O.P.S.
#
# This file is part of the Airtime project.
# http://airtime.sourcefabric.org/
#
# Airtime is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Airtime 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 Airtime; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# This script for a correct system environment for Airtime.
#
# Absolute path to this script

View File

@ -1,28 +1,4 @@
#!/bin/bash
#-------------------------------------------------------------------------------
# Copyright (c) 2010 Sourcefabric O.P.S.
#
# This file is part of the Airtime project.
# http://airtime.sourcefabric.org/
#
# Airtime is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Airtime 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 Airtime; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# This script sets stream setting(max bitrate and number of streams in Airtime.
#
# Absolute path to this script
SCRIPT=`readlink -f $0`
# Absolute directory this script is in

View File

@ -1,31 +1,4 @@
#!/bin/bash
#-------------------------------------------------------------------------------
# Copyright (c) 2010 Sourcefabric O.P.S.
#
# This file is part of the Airtime project.
# http://airtime.sourcefabric.org/
#
# Airtime is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Airtime 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 Airtime; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# This script imports audio files to the Airtime storageServer.
#
# To get usage help, try the -h option
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# Determine directories, files
#-------------------------------------------------------------------------------

View File

@ -1,92 +0,0 @@
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Copyright (c) 2010 Sourcefabric O.P.S.
#
# This file is part of the Airtime project.
# http://airtime.sourcefabric.org/
# To report bugs, send an e-mail to contact@sourcefabric.org
#
# Airtime is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Airtime 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 Airtime; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# This script converts an ICU localization file from Serbian Latin
# to Serbian Cyrillic.
#-------------------------------------------------------------------------------
import sys, re, codecs
usageString = 'Usage: serbianLatinToCyrillicConverter.py' \
' inputfile outputfile'
if len(sys.argv) >= 3:
fileNameIn = sys.argv[1]
fileNameOut = sys.argv[2]
else:
print usageString
sys.exit(1)
oldLines = codecs.open(fileNameIn, 'r', 'utf-8').readlines()
newLines = [ ]
def cyrillize(word):
if re.match(r'#.*#\Z', word):
return word
compound = { u'lj' : u'љ', u'Lj' : u'Љ',
u'nj' : u'њ', u'Nj' : u'Њ',
u'' : u'џ', u'' : u'Џ' }
simple = dict(zip(u'abvgdđežzijklmnoprstćufhcčšw',
u'абвгдђежзијклмнопрстћуфхцчшв'))
simple.update(dict(zip(u'ABVGDĐEŽZIJKLMNOPRSTĆUFHCČŠW',
u'АБВГДЂЕЖЗИЈКЛМНОПРСТЋУФХЦЧШВ')))
exceptions = { ur'\н' : ur'\n',
u'Фаде ин' : u'Фејд ин',
u'Фаде оут' : u'Фејд аут',
u'фаде ин' : u'фејд ин',
u'фаде оут' : u'фејд аут',
u'есцапе' : u'ескејп',
u'Плаy' : u'Плеј',
u'Паусе' : u'Поуз',
u'трацк' : u'трак',
u'УРИ' : u'URI',
u'РДС' : u'RDS',
u'БПМ' : u'BPM',
u'ИСРЦ' : u'ISRC' }
for latin, cyrillic in compound.iteritems():
word = word.replace(latin, cyrillic)
for latin, cyrillic in simple.iteritems():
word = word.replace(latin, cyrillic)
for bad, good in exceptions.iteritems():
word = word.replace(bad, good)
return word
for line in oldLines:
m = re.match(r'(.*)"(.*)"(.*)\n', line)
if m:
line = m.groups()[0] + '"' \
+ cyrillize(m.groups()[1]) + '"' \
+ m.groups()[2] + '\n'
elif line == 'sr_CS:table\n':
line = 'sr_CS_CYRILLIC:table\n'
newLines += [line]
codecs.open(fileNameOut, 'w', 'utf-8').writelines(newLines)