Using Bitmovin Player with Church Online Platform

Today’s post will be a brief tutorial on using Bitmovin‘s excellent HTML5 video player with Church Online Platform.

If you’re a church that is wanting to go live, and you haven’t discovered COP, it’s a marvelous product. The fine folks on the life.church Digerati Team (who created the Bible App and made it available on just about every platform known to mankind). It’s a free hosted platform that lets you deliver church online. All you have to do is bring your own streaming provider and provide an embed code. You can use your provider’s player, or you can use your own player. The Digerati team are also a client of mine, and I really enjoy working with them – they’re talented, nerdy, and very good at what they do. (most recently, I helped them build out their Wowza Streaming Engine capability for automating the scheduling and delivery of simulated live events.)

One of my favorite video players out there right now is from Bitmovin, and they provide a CDN-hosted player that provides excellent analytics (complete with API access for the especially nerdy), and usage is free for the first 5000 impressions (and pricing is quite reasonable as you scale up from there). For this reason alone, it’s an excellent choice for churches getting started with streaming. Its other major benefit is that because it is written in HTML5 and Javascript, it will work on just about anything you can throw at it (for the really archaic devices, it still has a Flash component). It also is designed from the ground up to support the new MPEG-DASH standard, but if you’re using a streaming CDN or service that doesn’t provide DASH, no big deal, as the player also supports HLS, even for Flash delivery for those 3 devices that still haven’t discovered modern streaming technology or are running a particularly ancient version of Android. Added bonus, BitMovin’s player also supports VR and 360 streaming (as does Wowza Streaming Cloud).

For starters, you’ll need to sign up for an account, which will give you player information. One thing you’ll want to make sure you do is add your churchonline.org domain to the allowed domains for your license key. This is under Player/Overview:

Bitmovin Player Domain Config

If you forget to do this, the player will simply show an error telling you you need to do it.  This keeps someone from using your player key on their site, so be sure to use yourdomain.churchonline.org, not just churchonline.org.

To put this in your COP page, go to the event where you wish to use the player, and go to the Video tab:

ChurchOnline Event Settings

When you go to the Embed menu, you will see code to put it on the page (under Default video embed code). This is a little more involved than your standard embed code.

Bitmovin Embed Controls

A couple of key things to note here with regards to COP:

  1. In order to put the <script> stuff in your <head> section, you’d need to create a custom theme in COP. This is not necessary (in fact, putting that script statement in the head that way doesn’t work). What you’ll need to do is simply put the <script> piece just above the rest of it in the default embed code section.
  2. You’ll need to edit the source section in that code. If all you’re doing is HLS, you can remove the dash and progressive entries. Leave the HLS entry in place and put in the HLS URL provided by your streaming platform. In the case of Wowza Streaming Cloud, this is located at the bottom of the Overview tab of your streaming application under “Playback URLs”.
  3. The “poster” entry is the image the player shows when you’re not streaming any video.

So, for my test stream, the embed code looks like this:


<script type="text/javascript" src="https://bitmovin-a.akamaihd.net/bitmovin-player/stable/7/bitmovinplayer.js"></script>

<div id="player"></div>
<script type="text/javascript">
var conf = {
key: "d8XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX2e",
source: {
hls: "http://wowzaprod103-i.akamaihd.net/hls/live/######/########/playlist.m3u8",
poster: "http://dontfenceme.in/wp-content/uploads/2013/09/g-global-background.jpg"
}
};
var player = bitmovin.player("player");
player.setup(conf).then(function(value) {
// Success
console.log("Successfully created bitmovin player instance");
}, function(reason) {
// Error!
console.log("Error while creating bitmovin player instance");
});
</script>

The console.log lines aren’t necessary, but potentially useful when trying to debug why it can’t instantiate the player.

If you want to run a separate video when the doors aren’t open, put that under the offline video embed code section. You can leave the mobile and low sections empty, as your stream is probably already adaptive from your streaming provider.

Save it, and this is what you get:

BitMovin in ChurchOnline Platform

In order to remove the Bitmovin logo, edit the theme’s CSS and add the following lines:


/* Remove Bitmovin Logo on player */
.bmpui-ui-watermark {
display: none;
}

ChurchOnline Platform CSS Edit

Inside Wowza Startup Packages

Startup packages are one of the more useful features of Wowza Media Server for EC2 – they allow you to custom-configure a system for rapid scaling and provisioning. Wowza provides several starter packages to build on.

A startup package is a file (up to 16384 bytes in size) that’s passed to the instance through the –user-data-file parameter on the API tools (if you’re uploading it via the AWS Web Console, you’ll need to encode it to Base64 and paste it into the text box) . There are a few ways that the data can get into the instance, which Amazon documents over here. For a generic EC2 instance, this can be anything, from text to binary data, depending on what the processing on the target instance is set up to do. In the case of Wowza, it’s a zip file with a specific structure. Much of this is digested from the Wowza for EC2 guide.

File Contents

A startup package for EC2 contains the following:

  • startup.xml (startup manifest)
  • tuning folder
  • wowza folder
  • Any other folders referenced in the startup manifest

A startup package is limited to a maximum of 16KB.

Startup activities are logged to /usr/local/WowzaMediaServer/logs/wowzamediaserver_startup.log. This is a good place to look if it’s not behaving as expected. The package is unpacked to /opt/working.

Startup Manifest

This file controls the startup processing for instantiating a Wowza server on Amazon EC2. It allows three commands: Install, Download, and RunScript.

Download

The <Download> command will download content from a web server and save it to the local Amazon instance. The <Download> command includes the following elements: URL, Data, Header, Destination, and Action:

<Download>
<URL>[URL]</URL>
<Data>[data]</Data>
<Header><Name>[key-name]</Name><Value>[value]</Value></Header>
<Header><Name>[key-name]</Name><Value>[value]</Value></Header>
<Destination>[relative-or-absolute-file-path]</Destination>
<Action>[UNZIP, INSTALL]</Action>
</Download>

The only two required elements are <URL> and <Destination>. To download a file from the url http://www.mycompany.com/myfile.zip, save it to the local machine at the location /opt/myfile.zip and unzip the file after download, the command is:

<Download>
<URL>http://www.mycompany.com/myfile.zip</URL>
<Destination>/opt/myfile.zip</Destination>
<Action>UNZIP</ Action >
</Download>

When completed, the contents of the zip archive are located in /opt.

One use of the <Download> command is to work around the 16kB startup package size limitation. For example, if you need to add several .jar files into the Wowza Server “lib” folder and these files push your startup package size over the 16kB limit, you might package these files into a separate zip archive. You can then host this zip archive on a web server and use the <Download> command to install the files into the Wowza Server “lib” folder.

It’s important to remember that the zipfile path structure is critical. If it uses no paths, you’ll need your destination to be where it ultimately lives, either in the staging area, or the absolute path to the Wowza install. When creating the zipfile with relative paths, create the path tree as if you were in the Wowza installation root.

URL

The <URL> is the URL of the file to be downloaded. The download can be performed over SSL by starting the url with https:// rather than http://. The url can also contain query parameters. The file will be downloaded using the GET method unless <Data> is specified.

Data

The Data is text data that will be included as part of the body of the HTTP request. You can use post data to send user name and password information to your web server so you can protect your content.

Header: <Name> and <Value>

The <Header> elements are name value pairs added to the header part of the HTTP request. An example would be:

<Header>
<Name>Content-type</Name>
<Value>text/plain</Value>
</Header>

Destination

The <Destination>element is the path to which the file will be saved (including the filename). This path can be relative or absolute. The base directory when calculating a relative file path, is the root directory of the startup package (the folder that contains the startup.xml file).

Action

The <Action> element is the action performed after the file is downloaded. The action can either be UNZIP or INSTALL. If the action is UNZIP the downloaded file will be unzipped using the unzip command. If the action is INSTALL the downloaded file will be unzipped and the contents of the folder will be installed (copied) into the Wowza Server installation folder.

Install

The <Install> command will copy the contents of a folder into the Wowza Server installation folder. The <Install> command can either contain a single <Package> element or single <Folder> element.

<Install>
<Package>[path-to-package]</Package>
</Install>
<Install>
<Folder>[foldername]</Folder>
</Install>

The Package path can reference an external URL, like http://wowzamediasystems.s3.amazonaws.com

The Folder path can reference either a relative path (relative to the root of the startup package where Startup.xml is located) or an absolute path on the local file system.

RunScript

The <RunScript> command will execute a script on a running Amazon instance.

<RunScript>
<Script>[relative-or-absolute-file-path]</Script>
<Param>[parameter]</Param>
<Param>[parameter]</Param>
</RunScript>

Script

The <Script> element is the path to the script file to be executed. This path can be relative or absolute. The base directory when calculating a relative file path, is the root directory of the startup package (the folder that contains the startup.xml file). This can refer to a script, or be a single-line command.

Any files referenced in the script need absolute paths or a path relative to the startup package root.

Param

The <Param> elements are parameters that will be passed to the running script. For example the following <RunScript> command:

<RunScript>
<Script>scripts/copyfile.sh</Script>
<Param>filea.txt</Param>
<Param>fileb.txt</Param>
</RunScript>

Would be the equivalent of executing the command:

./scripts/copyfile.sh filea.txt fileb.txt

Environment Variables

The following environment variables are available to scripts launched by the startup processor:

AWSEC2_METADATA_INSTANCE_ID - Amazon instance id
AWSEC2_METADATA_SECURITY_GROUPS - Security group
AWSEC2_METADATA_LOCAL_IPV4 - Local IP address
AWSEC2_METADATA_AMI_LAUNCH_INDEX - Launch index
AWSEC2_METADATA_PUBLIC_HOSTNAME - Public host name
AWSEC2_METADATA_PRODUCT_CODES - DevPay product code
AWSEC2_METADATA_INSTANCE_TYPE - instance type (m1-small, m1-large, m1-xlarge)
AWSEC2_METADATA_HOSTNAME - Public host name
AWSEC2_METADATA_LOCAL_HOSTNAME - Local host name
AWSEC2_METADATA_PUBLIC_IPV4 - Public IP address
AWSEC2_METADATA_AMI_MANIFEST_PATH - S3 manifest path
AWSEC2_METADATA_RESERVATION_ID - Instance reservation ID
AWSEC2_METADATA_AMI_ID - AMI ID

Wowza Folder

The Wowza folder in the startup package is meant to mirror the Wowza installation folder on the server. When the Install command in the startup manifest is invoked with this folder, the file structure of this folder will be copied to the installation folder.

Applications Tree

Contains folders for each Wowza application configured. There are not typically any files in this tree, just folders.

Conf Tree

Contains the configuration files for the server (at the root of the tree) and for each application (in folders matching the applications tree

Content Tree

Contains any content referenced by the applications. This is where SMIL files, stream schedules, and such go. Any audio/video content that goes here won’t fit in the startup package and will need to be downloaded separately.

Lib Tree

Contains any additional modules for the server.

Tuning Folder

This folder contains tuning scripts that tune the Wowza server.  It copies the requisite environment variables and tuning commands to a script executed as part of the Wowza startup, which happens after the startup processor is run.

Scripting

The following useful linux tools are available on the standard EC2 build (based on Fedora):

  • MySQL client commands
  • zip/unzip, bzip/bunzip, gzip/gunzip
  • s3fs
  • Compiler and build tools
  • WGet
  • RSync
  • SSH commands
  • dos2unix/unix2dos
  • curl
  • perl 5.8.8 (with MySQL support)
  • GPG
  • Shells: bash, sh
  • RRDTool
  • PHP5
  • EC2 API tools

Services

The following services are available on the EC2 build of Wowza, in startup order:

  • SNMP
  • SSH
  • FTP (Anonymous access to /var/ftp/)
  • MySQL (default password = “password”)
  • Wowza
  • Apache 2 – port 8080, content in /var/www/html
    • Cacti
  • Java Console

Startup package scripts and data are invoked by the Wowza startup script. If you modify any applications started prior to that, you’ll need to restart them.

Example

Here’s what my startup.xml looks like:

<Startup>
 <Commands>

 <!--
  Comments
  -->

 <Download>
  <URL>http://webserver/wowza/wms-plugin-collection.zip</URL>
  <Destination>wowza/lib/wms-plugin-collection.zip</Destination>
  <Action>UNZIP</Action>
 </Download>

 <RunScript>
  <Script>scripts/mount-s3.sh</Script>
 </RunScript>

 <Install>
  <Folder>wowza</Folder>
 </Install>

 <RunScript>
  <Script>tuning/tune.sh</Script>
 </RunScript>

 <RunScript>
 <Script>scripts/enable_cacti.sh</Script>
 </RunScript>
 </Commands>
</Startup>

How this works:

  • Download the wms-plugin-collection.zip file and dump it in the staging area for wowza (/opt/working/wowza/lib)
  • Unzip it (this leaves the zip file behind, but that doesn’t matter)
  • Run the a script that mounts some s3 buckets and copies them into the content folder:
#!/bin/sh
mkdir -p /usr/local/WowzaMediaServer/content/s3
mkdir -p /usr/local/WowzaMediaServer/content/archives

s3fs bucket1 -o accessKeyId=XXX -o secretAccessKey=YYY /usr/local/WowzaMediaServer/content/s3

s3fs bucket2 -o accessKeyId=XXX -o secretAccessKey=YYY /usr/local/WowzaMediaServer/content/archives/

cp /usr/local/WowzaMediaServer/content/s3/* /usr/local/WowzaMediaServer/content
  • Install the Wowza configs from the staging area
  • Run the tuning scripts
  • Run a script that automatically enables Cacti (since polling the local host is disabled by default):
#!/bin/sh
mysql -u root -ppassword < scripts/enable_cacti.sql

enable_cacti.sql contains the following statement:

update cacti.host set disabled='' where id='2';

(note that if you’re using an Elastic IP, you’ll need to restart the Wowza Service for Cacti to behave)

In my Wowza directories, I have:

  • applicationsdirectory
    • live directory (think of this as a mount point – it’s an empty directory, but has to exist)
  • confdirectory
    • Server.xml (general server parameters)
    • VHost.xml (host bindings, HTTP providers, etc.)
    • livedirectory (this is the application configuration for the live application)
      • Application.xml (defining the live application)
  • content directory
    • ipad.smil ( multi-bitrate stream selection for iOS devices)
    • mobile.smil (defining where the Roku stream goes – this abstracts a potentially changing stream name, as well as giving me a way to track Roku traffic using this perl stats collection script)
    • streamschedule.smil (defines the schedule for the Stream Class module)
    • Additional material is pulled from S3 in the aforementioned script
  • lib directory (empty mount point)

To start my Wowza instances, I create the startup package file and tree structure, and then call this startup script that packs up the zip file and fires off the instance.

Using the Wowza Stream Class

I mentioned in the previous post about using ffmpeg in a cron job to create Simulated Live events via Wowza. In this post, I’ll explain how to do it using the Wowza Stream Class module, which allows you to set a broadcast schedule to play a mix of recorded and live content.

Wowza has a pretty good document on how to add this module in to your server and do a test playlist.If you’re setting this up on Amazon EC2, you’ll need to update your startup package by putting the module in the wowza/lib directory and the playlist in the wowza/content directory

Unfortunately, the tutorial doesn’t really cover playlist creation beyond the example. This is especially tricky, given that the scheduling parameters don’t seem to conform to any known SMIL standard. Yes, it’s XML, so theoretically, it doesn’t matter, but there are extensions in SMIL 3.0 that are meant to deal with server-side playlists for automating programming.

Unless you specified a different application name in the Properties section of Server.xml, the automated playlist will publish to the live application.

The basic structure of the SMIL file body consists of <stream> and <playlist> statements.

Stream Element

The <stream> element defines one or more virtual stream names that the playlists will feed into:

<stream name="playlist-high"></stream>
<stream name="playlist-low"></stream>

In this example, I have a high and low bandwidth stream. In your player, you reference the stream name, rather than the streamshedule.smil file, like this:

Flash RTMP:

streamer: rtmp://wowza.server.address(:port)/live
file: playlist-high

Flash HTTP:

http://wowza.server.address/live/playlist-high/manifest.f4m

HLS:

http://wowza.server.address/live/playlist-high/playlist.m3u8

Silverlight

http://wowza.server.address/live/playlist-high/Manifest

Playlist element

The <playlist> element defines specific video sequences that go into the virtual stream. There are four key parameters to the playlist element:

  • name : This is a unique name for that particular sequence.
  • playOnStream : This tells the Stream Class module which of the previously defined streams this playlist is associated with.
  • repeat : Valid values are true/false. This defines whether this playlist loops when it gets to the end.
  • scheduled : When this playlist is scheduled, in the format “YYYY-MM-DD HH:MM:SS” (24-hour time)

Within the playlist element are one or more <video> statements that use the following parameters:

  • src: the video to be played. Can either be:
    • a stream within the same live application (use the stream name only)
    • an MP4 video file in the Wowza content directory (use mp4:filename.mp4)
    • A stream elsewhere (requires some additional modules)
  • start : The number of seconds into the video to start playing. If this is a live source, use the value -2.
  • length : The number of seconds to play the video. The value -1 indicates to play until it ends.

Using start/length is a useful way to introduce commercial breaks or intermissions into a stream. This example would show BigBuckBunny.mp4 from the start, for 60 seconds, then cut to a commercial for the duration of the advertisement-1.mp4 file. After the commercial, it would resume and play for 2 more minutes, play a 30-second commercial from advertisement-2.mp4 and then plays the rest of the BigBuckBunny.mp4 file. If the playlist set to repeat, this will loop.

<video src="mp4:BigBuckBunny.m4v" start="0" length="60"/>
<video src="mp4:advertisement-1.mp4 start="0" length="-1"/>
<video src="mp4:BigBuckBunny.m4v" start="60" length="120"/>
<video src="mp4:advertisement-2.mp4 start="0" length="30"/>
<video src="mp4:BigBuckBunny.m4v" start="180" length="-1"/>

When a particular playlist has ended, and there are no others currently scheduled, it will default to the last playlist, even if that playlist’s repeat is set to false.

Here’s an example for our weekly service replays, and live sunday events:

<smil>
 <head>
 </head>
 <body>

  <stream name="playlist-high"></stream>
  <stream name="playlist-low"></stream>

  <playlist name="mon-l" playOnStream="playlist-low" repeat="false" scheduled="2011-01-17 07:45:00">
   <video src="mp4:traditions-l.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="mon-h" playOnStream="playlist-high" repeat="false" scheduled="2011-01-17 07:45:00">
   <video src="mp4:traditions-h.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="mon-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-17 09:30:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="mon-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-17 09:30:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="tue-l" playOnStream="playlist-low" repeat="false" scheduled="2011-01-18 12:45:00">
   <video src="mp4:praise-l.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="tue-h" playOnStream="playlist-high" repeat="false" scheduled="2011-01-18 12:45:00">
   <video src="mp4:praise-h.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="tue-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-18 14:30:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="tue-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-18 14:30:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="wed-l" playOnStream="playlist-low" repeat="false" scheduled="2011-01-19 21:45:00">
   <video src="mp4:praise-l.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="wed-h" playOnStream="playlist-high" repeat="false" scheduled="2011-01-19 21:45:00">
   <video src="mp4:praise-h.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="wed-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-19 23:30:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="wed-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-19 23:30:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="thu-l" playOnStream="playlist-low" repeat="false" scheduled="2011-01-20 03:15:00">
   <video src="mp4:traditions-l.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="thu-h" playOnStream="playlist-high" repeat="false" scheduled="2011-01-20 03:15:00">
   <video src="mp4:traditions-h.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="thu-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-20 05:00:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="thu-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-20 05:00:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="fri-l" playOnStream="playlist-low" repeat="false" scheduled="2011-01-21 07:45:00">
   <video src="mp4:traditions-l.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="fri-h" playOnStream="playlist-high" repeat="false" scheduled="2011-01-21 07:45:00">
   <video src="mp4:traditions-h.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="fri-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-21 09:30:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="fri-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-21 09:30:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sat-l" playOnStream="playlist-low" repeat="false" scheduled="2011-01-22 02:45:00">
   <video src="mp4:praise-l.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sat-h" playOnStream="playlist-high" repeat="false" scheduled="2011-01-22 02:45:00">
   <video src="mp4:praise-h.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sat-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-22 04:30:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sat-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-22 04:30:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sun-am-high" playOnStream="playlist-high" repeat="false" scheduled="2011-01-23 11:30:00">
   <video src="mobile-2" start="-2" length="6300"/>
  </playlist>

  <playlist name="sun-am-low" playOnStream="playlist-low" repeat="false" scheduled="2011-01-23 11:30:00">
   <video src="mobile-1" start="-2" length="6300"/>
  </playlist>

  <playlist name="sun-am-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-23 13:15:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sun-am-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-23 13:15:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sun-pm-high" playOnStream="playlist-high" repeat="false" scheduled="2011-01-23 17:45:00">
   <video src="mobile-2" start="-2" length="6300"/>
  </playlist>

  <playlist name="sun-pm-low" playOnStream="playlist-low" repeat="false" scheduled="2011-01-23 17:45:00">
   <video src="mobile-1" start="-2" length="6300"/>
  </playlist>

  <playlist name="sun-pm-loop-h" playOnStream="playlist-high" repeat="true" scheduled="2011-01-23 19:30:00">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="sun-pm-loop-l" playOnStream="playlist-low" repeat="true" scheduled="2011-01-23 19:30:00">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="default-high" playOnStream="playlist-high" repeat="true" scheduled="2011-01-01 00:00:01">
   <video src="mp4:1-16-Loop-H.mp4" start="0" length="-1"/>
  </playlist>

  <playlist name="default-low" playOnStream="playlist-low" repeat="true" scheduled="2011-01-01 00:00:01">
   <video src="mp4:1-16-Loop-L.mp4" start="0" length="-1"/>
  </playlist>

 </body>
</smil>

Video files can either be uploaded, or recorded on the server using a live-record application type.

Once your playlist is built, you’ll need to restart the Wowza service for it to read the new playlist in and schedule it internally.

Update(July 18, 2001) : I’ve added a post about my Excel playlist generator.

Simulated Live with Wowza and Apple HTTP Live Streaming

Last summer, we switched our primary Flash streaming over to 316 Networks partly because of the simulated live capability they offered, and partly for the Media Suite backend. We continued to use Wowza on EC2 for our mobile users, since the solution works very well. Unfortunately, simulated doesn’t work for our mobile users, who are limited to live.

We have 3 replays a week for each of our two web services: the traditional service from Sunday morning, and the praise service from Sunday night.

I should probably step back for a moment and explain what “Simulated Live” means. It’s a recorded event, but from a user standpoint, it behaves like a live event. There’s a set start time, and if you come in 20 minutes after the start of the event, you get the video 20 minutes in; there are no “trick play” DVR functions like fast-forward or rewind. This gives a shared experience for everyone who is watching, and also keeps us legit with the copyright restrictions, as our “live” events are considered extensions of the actual live event in the sanctuary. Simulated live is also known as “pseudostreaming”

Wowza doesn’t natively support pseudostreaming (although the Stream Class API does have some scheduling capability — see this post), so we needed some way of broadcasting recordings on a schedule. I could use the VT5 machine and the Scheduler to replay the recordings on a schedule, but the big downside is that it consumes local bandwidth, which is in short supply.

What I needed was a way of streaming the archive files (high and low bandwidth) that were created by the Kulabyte encoder during the live event. Ideally, since I already upload these to 316 for rebroadcast, I’d like to not have to upload it to two locations.

Enter the lovely open-source encoder, ffmpeg. My concern about using ffmpeg was that re-encoding an already encoded file had the potential of introducing compression artifacts and adding CPU load. I was very happy to find an obscure command-line setting that tells it to copy the input file’s audio and video codec directly. The only thing ffmpeg would be doing would be extracting the audio and video streams from the MPEG-4 container and stuffing them into a Flash container without molesting the actual streams at all. Added bonus: ffmpeg can also not only output to RTMP, but can take RTMP as an input as well.

Fortunately, Media Suite’s media bin makes videos available via direct RTMP and HTTP and helpfully provides the CDN URL for those files. Another method of storage would be to use Amazon S3 and get the files either via HTTP or Cloudfront RTMP.

Attempt #1: Installed ffmpeg on a local Ubuntu box via apt-get, no dice. It refused to connect to the RTMP server. After some research, I found that the version of ffmpeg on the Jaunty version of Ubuntu is 0.50, and seems to have some weird build options.

Attempt #2: I downloaded the ffmpeg 0.61 source, ran a quick configure/make/make install on it, and tried again. Success! I was taking an MP4 recording on a disk, and streaming to Wowza. I then changed the input from a local file to to the URL provided by 316, and weird stuff started happening. And then I realized that the URL had some GET parameters in it that left a question mark and two ampersands in the URL that I needed to escape before bash would parse it correctly. Once I fixed that, it started running happily, and I was seeing the stream on my iPod.

Attempt #3: I Downloaded the ffmpeg 0.61 source to my Wowza server on EC2 and crossed my fingers that the build would go off without a hitch. Success! I then transplanted the command line I used on my test box, tweaked the destination server to the local Wowza install, and fired it off. Success again!

Now that I’d proved the concept, it was simply a matter of putting that command into a cron job and waiting to see if it fired off. And then realizing that the server is in eastern time. And then realizing that I should probably correctly specify the path to ffmpeg (doh!). But once I got those silly details ironed out, off it went.

So now I have ffmpeg on my EC2 system, consuming virtually no CPU, pulling my archive files from 316 (only had to upload them once), streaming to Wowza on a scheduled basis, without chewing up T1 bandwidth at our main site.

Unfortunately, there’s a little bit of brain damage involved in pulling it from 316, because I’ll have to go change the filename in the cron job every week. Perhaps I’ll end up uploading it to S3 after all and just giving it a static name.

Here’s the commandline used:

/path_to/ffmpeg -i rtmp://rtmp.server/filename.mp4 -re -sameq -acodec copy -vcodec copy -f flv rtmp://localhost/live/stream

Commandline options used:

  • -i : specifies input. This can be either RTMP, HTTP, or a local file.
  • -re : near-realtime mode
  • -sameq : Keep quality settings
  • -acodec copy : send input audio stream unmolested to the output
  • -vcodec copy : send input video stream unmolested to the output
  • -f flv : Force output to FLV container

Sony VISCA RS-422 Control

Update – January 2014: Wow, 3 years later this is still one of the most popular posts on this blog! I’ve had some questions about using this with the EVI-HD1, which has only RS-232 DIN ports. Theoretically, it should work, but you might need to alter some pinouts in the breakout box, and I would highly recommend using shielded/grounded cable, as RS-232 is an unbalanced signal. A reader is going to give it a try, and if it’s successful, I’ll update the post with some pictures.

Update – October 2021: It blows my mind that nearly 11 years later, this is still one of the most frequently visited posts on the site…

Now, back to our regularly scheduled blog post!

We recently acquired a few more of Sony’s excellent EVI-D70 cameras for use in our chapel for streaming weddings, funerals, and other events in our smaller worship space.

When we remodeled the space a few years back, it was originally designed with these cameras in mind.  The original intent was to provide some additional angles for videographers to use, but the idea never really took off. Due to lack of use, the cameras originally installed were re-purposed for Resurrection Online in the main sanctuary. Things have come full circle now, and the ability to stream services and events from this space is being requested. As a result, we acquired some more cameras, and are in the process of updating the camera system in that room.

The original design used an AMX touchscreen/joystick controller and a custom integration over RS-232, with each camera homerun to the control rack. There were numerous difficulties with the cameras randomly freezing up and not responding to controls, requiring someone to get on a ladder and power-cycle the unit.

As part of the updated system, we’ve ditched the AMX controller and are using Sony’s RM-BR300 control unit which is designed for this particular camera. We also have user familiarity, since we already have one of these controllers in our main sanctuary for the BRC-H700 remote camera mounted on the catwalks (aka, the “SkyCam”). The controller can do Sony’s VISCA protocol over RS-232 (via a Mini-DIN) or RS-422 (via a Phoenix connector).

This is where it got sticky. We have an 8-conductor homerun cable from each camera position, but the Sony controller is designed to daisy-chain the VISCA ports. Each camera has two RS-232 Mini-DIN ports (one in, one out). Fortunately, both RS-422 and RS-232 in this application only require four wires, so we can loop out and back on the same cable.

Due to the annoyance factor of having to re-terminate Mini-DIN connectors, I opted for the RS-422 port which uses a Phoenix screw terminal (Part # 1840434 in case you need to order one – Sony wants an obscene amount of money for them, they’re dramatically cheaper at an electronics supplier like Mouser). RS-422 also has the advantage of much longer signal path due to its balanced signal. Since we’re also adding a new location, I wanted to be able to wire it up with standard Category 6 twisted-pair cabling. This cable also has eight conductors, making it ideal for the task. In terms of flexibility, RJ-45 is king in the twisted-pair world, so I had do design a means of daisy-chaining my VISCA ports via ordinary patch cords.

At first, I was a little baffled by the wiring of VISCA, since the RM-BR300 connector pinout is exactly backwards from that of the one on the cameras, and the documentation provided is a little confusing. Fortunately, the Sony POSC was quick to help and they e-mailed me a wiring diagram for this specific application (and were kind enough to allow me to post it. I translated that into two main components, a breakout box and a standard cable, that would work on either the controller or the cameras.

To make the cable, I simply took a patch cord off the shelf, lopped one end off, and terminated it on the Phoenix connector:

The wiring is as follows:

Now, you’ll notice my wiring diagram shows the orange pair on the first two, and the picture shows green. This is because I found out (after much frustration of tracing signals) that the patch cord I grabbed happened to be wired for 568A rather than the more common 568B. Simply swap orange and green if this is the case.

Once I got the cables sorted out, I then replicated Sony’s wiring diagram with a handful of data jacks. The connections go like this:

I used bits I had on the shelf, but I would recommend using a different jack color for the control input so you don’t get it confused. Once I got it wired up, this is what I had (I colored the control jack black with a Sharpie):

Even if this install only has three cameras, I wired it up for five, to fill a six-way biscuit box that I had on the shelf (these are Lucent/Avaya components):

.. and put the lid on it with some labels:

As for the hookup, set the DIP switches on the bottom of the controller and the cameras to use RS-422 and either 9600 or 38400 bps, and hook them up. Note that they must be in sequence, or the whole chain will be broken if you skip a slot. Plug a camera into #1, it will be #1 on the controller after they self-enumerate on startup, in order of closest to farthest on the chain. Connecting a camera will cause the controller to re-initialize.

Action Shot:

I used a biscuit box, but you could also use a modular patch panel to do the same thing. I hope to use a second category 6 run with an S-Video termination on it (2 pairs) and power (other 2 pairs) so that the whole system can run off a standard 2-cable pull.

Browser-Aware Player

One of the big challenges of streaming to the web is the sheer diversity of devices out there.

This past week, I pushed out some modifications to the player code on our live page that switches the player code based on what the user is connecting with. The genesis of this change was a problem with our change to JW Player Version 5 causing our PlayStation users to no longer be able to watch our video since JW v5 requires Flash 10 and Sony apparently doesn’t care about its customers. After a successful test with the Playstation, I extended the code to provide an HTML5 <VIDEO> tag for our iPhone users (allowing us to clear up some the clutter on the sidebar), as well as MMS and RTSP links around a graphic mimicking the Flash-based player in order to provide a consistent user experience for our Android/WebOS/BlackBerry/WinMo users.

EDIT: The main reason I’m not doing straight HTML5 with Flash fallback (a much more elegant solution) is that we’re sending out VP6 for our flash users and a lower-bandwidth h.264 stream for our mobile users. We’re not currently using h.264 for our flash users because of the poor quality of the h.264 encoder in Flash Media Live Encoder. Once we get a “real encoder“, we’ll send out a single set of h.264 streams and use HTML5 with fallback.

The code is here.

Anatomy of an online worship service

(or, How Amazon Cloudwatch helps manage Wowza server load)02-21-10-AM-AWS

This morning I woke up to two things: Beautiful Kansas City February weather (aka, an ice storm), and a voicemail from the Senior Pastor, asking if we had sufficient online capacity to support a larger-than-usual stream audience. Online worship streaming is a great option for these weather events that have been so common this winter (and not just in the KC area – we see increased online attendance when the weather gets foul elsewhere, like the DC storms of a few weeks ago).

My first indication that this was going to be a big event was Woopra showing 30 people on the web page half an hour before we start sending any kind of video (which is itself 75 minutes before we actually start the morning service). Usually there are two or three. Fifteen minutes after we started sending video, we were already cranking out 20-30 streams (again, we usually only have a small handful at this point).

02-21-10-AM-CPU

AWS CPU Usage

Most weeks, we run two Wowza repeaters pulling from a single origin server, which gives us plenty of capacity. I had to spin up a third repeater by the beginning of the pre-service music, a fourth about 10 minutes later, and a fifth after five more minutes. I set my threshold for spinning a new server at 75% CPU on the repeaters, as indicated by the AWS CloudWatch monitors. In the case of a heavy influx of viewers, this gives the new instance enough time to get up and running before the other repeaters hit 100% CPU.  Wowza tells me this is at about 180Mbit/sec on a small instance, which for us means around 300 streams. The CPU threshold of 75% works out to about 260 streams.

Unfortunately for our online worshipers, our web server was bogging down pretty hard at

Web Server CPU usage

Web Server CPU usage

the beginning of the service, where the two CPU cores were maxed out for about 15-20 minutes, which translated into slower page loads. The database server wasn’t sweating too hard, so I suspect this could have been helped with better PHP caching. Fortunately for me, this had the effect of slowing down the rate of incoming streams, which allowed me to get new repeaters going before the existing ones started choking.

You can see in the graph where we added new repeaters, and how fast they ramped up. It also shows how incredibly well Wowza’s built-in load balancing works. We eventually leveled out at a little over 1100 streams, which meant our EC2 instances were cranking out 600-700 Mbps for nearly an hour:

AWS Network Usage

AWS Network Usage

Meanwhile, this is what we were seeing on Woopra (note the fortunate souls escaping the ice storm in Aruba and the Cayman Islands!):

2-21-10-AM

Next step is to define rules in Cloudwatch for automatically scaling. For that to work, I’m going to need to build my own Wowza AMI, since the current method of starting repeaters involves sending the instance a startup package from the client. I’ll need to build this configuration into the server for CloudWatch scaling to work properly.

Y2K10 in JavaScript?

On our live stream page, we have a nifty little javascript counter that lets you know when the next service is.

Leo noticed today that in Internet Explorer, it’s counting down properly, while in Firefox, it’s saying the event is already happening. On a hunch, we changed the target date to 12/31, and it started working properly again.

So, IE’s Javascript is smart enough to figure out that on December 29, the target date of January 3 is likely to be the one next week. Firefox is clinging to the past and assuming that I really meant the January 3 that happened 51 weeks ago.

How is it that the same script can be interpreted so differently within the same language on two different browser platforms? This stuff is supposed to be standard!