The CaNerdIan

Wowza Stream Scheduler Hacks: Google Calendar

Code, streaming, Wowza iCal, PHP, Stream Scheduler
Wowza Stream Scheduler Hacks: Google Calendar

One of Wowza’s most underutilized yet most powerful features is the stream scheduler. I’ve blogged about it extensively in the past, and I’ll return from a long hiatus to do it again. To recap some of the things you can do with this add-on:

ini\\_set("allow\\_url\\_fopen", 1);
error\\_reporting(0);
date\\_default\\_timezone\\_set("US/Central");

Using Evert’s conversion function, we get the schedule into an XML object:

$calUrl = "https://calendar.google.com/calendar/ical/xxxxxxxxxxxxx8%40group.calendar.google.com/private-xxxxx/basic.ics";
// get your private calendar URL from the calendar settings.
$CalData=file\\_get\\_contents($calUrl);
$xmlString=iCalendarToXML($CalData);
$xmlObj = simplexml\\_load\\_string($xmlString);

The object now looks like this: [code] SimpleXMLElement Object ( [PRODID] => -//Google Inc//Google Calendar 70.9054//EN [VERSION] => 2.0 [CALSCALE] => GREGORIAN [METHOD] => PUBLISH [X-WR-CALNAME] => Wowza Event Scheduler Calendar [X-WR-TIMEZONE] => America/Chicago [X-WR-CALDESC] => SimpleXMLElement Object ( ) [VEVENT] => Array ( [0] => SimpleXMLElement Object ( [DTSTART] => 20161017T160000Z [DTEND] => 20161017T170000Z [DTSTAMP] => 20161016T182755Z [UID] => 72klt8s5ssrbjp9ofdk8ucovoo@google.com [CREATED] => 20161012T212924Z [DESCRIPTION] => mp4:video1.mp4\,0\,-1 [LAST-MODIFIED] => 20161016T164138Z [LOCATION] => teststream [SEQUENCE] => 3 [STATUS] => CONFIRMED [SUMMARY] => 11am Broadcast [TRANSP] => OPAQUE ) [1] => SimpleXMLElement Object ( [DTSTART] => 20161017T170000Z [DTEND] => 20161017T180000Z [DTSTAMP] => 20161016T182755Z [UID] => ac3lgjmjmijj2910au0fnv5vig@google.com [CREATED] => 20161016T164116Z [DESCRIPTION] => mp4:video2.mp4\,0\,1800\nmp4:video3.mp4\,0\,1800 [LAST-MODIFIED] => 20161016T164118Z [LOCATION] => teststream [SEQUENCE] => 1 [STATUS] => CONFIRMED [SUMMARY] => Noon Broadcast [TRANSP] => OPAQUE ) ) ) [/code] So now we need to create another XML object for our schedule and give it the basic structure:

$smilXml = new SimpleXMLElement('');
$smilHead = $smilXml->addChild('head');
$smilBody = $smilXml->addChild('body');

Now we need to iterate once through the VEVENT objects to get stream names:

$playonstream = [];
foreach ($xmlObj->VEVENT as $event) {
$loc = $event->LOCATION;
$playOnStream["$loc"]=true;
// We don't really care about the value of this array element, as long as it exists.
// This way we only get one array element for each unique stream name
}
// Iterate through the list of streams and create them in the SMIL
foreach ($playOnStream as $key => $value) {
$smilStream = $smilBody->addChild('stream');
$smilStream->addAttribute('name',$key);
}

So now we have the beginnings of a schedule:

xml version="1.0"?

We now need to iterate through the list again to add in the fallback items for each stream that starts when the stream starts (this is done as a separate loop to keep the output XML cleaner):

// Add in default fallback entries
foreach ($playOnStream as $key => $value) {
$defaultPl=$smilBody->addChild('playlist');
$defaultPl->addAttribute('name',"default-$key");
$defaultPl->addAttribute('playOnStream',$key);
$defaultPl->addAttribute('repeat','true');
$defaultPl->addAttribute('scheduled',"2016-01-01 00:00:01");
$contentItem = $defaultPl->addChild('video');
$contentItem->addAttribute('src','mp4:padding.mp4');
$contentItem->addAttribute('start','0');
$contentItem->addAttribute('length','-1');
}

Which then gives us these new items:

[](mp4:padding.mp4)

And then we need to iterate again through the VEVENTS to create the actual schedule items:

foreach ($xmlObj->VEVENT as $event) {
//parse the times into Unix time stamps using the ever-useful strtotime() function;
$eventStart = strtotime($event->DTSTART);
$eventEnd = strtotime($event->DTEND);
//format them into the ISO 8601 format for use in the schedule
//Note that we're using H:i:s rather than h:i:s because 24-hour time is important here
$start = date("Y-m-d H:i:s", $eventStart);
$end = date("Y-m-d H:i:s", $eventEnd);
//extract summary for playlist name
$plName = $event->SUMMARY;
$plLoc = $event->LOCATION;
//extract description for content
$description = $event->DESCRIPTION;
$videos=preg\\_split('/\\\\n/',$description);
// add on a padding video at the end of this list
$videos[]="mp4:padding.mp4";
//create playlist
$playlist = $smilBody->addChild('playlist');
$playlist->addAttribute('name',$plName);
$playlist->addAttribute('playOnStream',$plLoc);
$playlist->addAttribute('repeat','false');
$playlist->addAttribute('scheduled',$start);
//iterate through playlist items
foreach($videos as $plItem) {
echo "$plItem\n";
$attrs=preg\\_split('/\\\\,/',$plItem);
// set defaults for stream start/duration if not specified
// assume start at beginning and play all the way through
if(!$attrs[1]) { $attrs[1] = 0; }
if(!$attrs[2]) { $attrs[2] = -1; }
$contentItem = $playlist->addChild('video');
$contentItem->addAttribute('src',$attrs[0]);
$contentItem->addAttribute('start',$attrs[1]);
$contentItem->addAttribute('length',$attrs[2]);
} // end of playlist loop
} // end of event loop

And, finally, we need to add a little bit of code to format the XML object for use with Wowza:

$dom = dom\\_import\\_simplexml($smilXml)->ownerDocument;
$dom->formatOutput = true;
$output=$dom->saveXML();
echo "$output\n"; // outputs to STDOUT
$dom->save('streamschedule.smil'); // save to file

For the purposes of this last section, I’ve created some additional events to add a secondary stream: Schedule Overview 11am Broadcast Event 11am Alternate Broadcast Event Noon Broadcast Event Event Broadcast The iCal looks like this: [code] BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN METHOD:PUBLISH X-WR-CALNAME:Wowza Event Scheduler Calendar X-WR-TIMEZONE:America/Chicago X-WR-CALDESC: BEGIN:VEVENT DTSTART:20161017T160000Z DTEND:20161017T170000Z DTSTAMP:20161016T182604Z UID:8m4hcp98fmuosuoe48o20drl7k@google.com CREATED:20161016T180813Z DESCRIPTION:mp4:video5.mp4\nmp4:video6.mp4 LAST-MODIFIED:20161016T180813Z LOCATION:altstream SEQUENCE:0 STATUS:CONFIRMED SUMMARY:11am alternate broadcast TRANSP:OPAQUE END:VEVENT BEGIN:VEVENT DTSTART:20161017T180000Z DTEND:20161017T190000Z DTSTAMP:20161016T182604Z UID:c1ijl48srikc22vkkvrgvpb718@google.com CREATED:20161016T180725Z DESCRIPTION:mp4:video4.mp4 LAST-MODIFIED:20161016T180725Z LOCATION:teststream SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Event TRANSP:OPAQUE END:VEVENT BEGIN:VEVENT DTSTART:20161017T160000Z DTEND:20161017T170000Z DTSTAMP:20161016T182604Z UID:72klt8s5ssrbjp9ofdk8ucovoo@google.com CREATED:20161012T212924Z DESCRIPTION:mp4:video1.mp4\,0\,-1 LAST-MODIFIED:20161016T164138Z LOCATION:teststream SEQUENCE:3 STATUS:CONFIRMED SUMMARY:11am Broadcast TRANSP:OPAQUE END:VEVENT BEGIN:VEVENT DTSTART:20161017T170000Z DTEND:20161017T180000Z DTSTAMP:20161016T182604Z UID:ac3lgjmjmijj2910au0fnv5vig@google.com CREATED:20161016T164116Z DESCRIPTION:mp4:video2.mp4\,0\,1800\nmp4:video3.mp4\,0\,1800 LAST-MODIFIED:20161016T164118Z LOCATION:teststream SEQUENCE:1 STATUS:CONFIRMED SUMMARY:Noon Broadcast TRANSP:OPAQUE END:VEVENT END:VCALENDAR [/code] And when we run the process, we get this spiffy code coming out:

[](mp4:padding.mp4)

[](mp4:padding.mp4)

[](mp4:video5.mp4)
[](mp4:video6.mp4)
[](mp4:padding.mp4)

[](mp4:video4.mp4)
[](mp4:padding.mp4)

[](mp4:video1.mp4)
[](mp4:padding.mp4)

[](mp4:video2.mp4)
[](mp4:video3.mp4)
[](mp4:padding.mp4)

So there you have a relatively simple one-way hack to spit Google Calendar/iCal events out into a Wowza Schedule. You would still need to manually run this every time you wanted to update the broadcast schedule (and reload the Wowza server), and this does not send any confirmation back to your iCal that the event has been scheduled. Stay tuned for a variation on this code that uses the Google Calendar API (a much more elegant approach)