Flowplayer · AWS signed URLs on demand

Now playing:

resolved video.url will be displayed here

 

HTML5 video poses the following constraints in conjunction with AWS signed URLs:

  1. Seeking to unbuffered positions does not work when the URL has already expired. This is not a Flowplayer specific issue, but affects HTML5 video in general, as can be verified at this demo. By consequence short expiry times cannot be used; as a workaround we set the expiry time for HTML5 video to the duration of the movie. Flash does not have this problem. For testing purposes you can switch to force Flash over RTMP for comparison.
  2. Devices which do not allow automatic playback on page load (e.g. iOS) also prevent video loading as result of an ajax call. Therefore the expiry time is even longer, an estimated maximum duration of a page visit.
  3. We use a splash setup to demonstrate how the player can be loaded successfully on demand - except for case 2. above. Reloading refreshed signed URLs can be tested by unloading the player - either by clicking the close button or via the q keyboard shortcut.

Summary: The unavoidable workarounds described in 1. and 2. lower the effectiveness of content protection dramatically. You have to decide whether it is worth the effort to implement and maintain such an involved setup which suggests protection, but delivers on its promise only in a limited way.

If you are serious about content protection a superior approach is encrypted HLS because protection is an optional conceptual part of the media itself. Check out the demo.

<head/>

<style>

#player {
background-color: #000;
background-image: url(//flowplayer.org/media/img/demos/minimalist.jpg);
}
@media(-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
#player {
background-image: url(//flowplayer.org/media/img/demos/minimalist@x2.jpg);
}
}
/* avoid vertical jumping due to delay of ajax call */
#player .fp-ratio {
padding-top: 41.67%;
}

CSS

<script>

To save time consuming Ajax calls we mimic Flowplayer's video type picking at page load and set up only one video type.

$(function () {
var api,
vtype = "video/flash",
 
fpajax = function () {
var flash = /flash/.test(vtype),
// rtmp does not need long expiry time
// devices not allowing autoplay or loading upon ajax
// cannot load on demand
expiry = flash ? 6 : flowplayer.support.firstframe ? 40 : 3600,
// over http sign full url, over rtmp sign only server path
videourl = (flash ? "" : "http://d2yz3vc7rxs49u.cloudfront.net/") +
"bauhaus" +
(flash ? "" : "." + vtype.replace("video/", ""));
 
$.ajax({
url: "http://flowplayer.blacktrash.org/getsignedurl.php?expires=" + expiry,
data: {
"movie": videourl
},
dataType: "text",
 
success: function (data) {
var clip = {
sources: [
{ type: vtype, src: (flash ? "mp4:" : "") + data }
]
};
 
if (api === undefined) {
// initial install
api = flowplayer("#player", {
ratio: false, // already set via CSS
share: false,
splash: true,
rtmp: "rtmp://s2istbhvi8cd52.cloudfront.net/cfx/st",
clip: clip
 
}).on("ready", function (e, api, video) {
// show video info, for demo only
var expiry = new Date(video.src.replace(/.*Expires=(\d+).*/, "$1") * 1000);
$(".expires").text(" (expires at " + expiry.toLocaleTimeString() + "):");
$(".signed pre").text(video.url);
 
}).on("finish", function (e, api) {
// refresh RTMP signature on replay
if (flash) {
api.one("resume", fpajax);
}
 
});
 
// now override the default ui click handle in splash state
$("#player .fp-ui").on("click", function (e) {
if (api.splash) {
e.stopPropagation();
 
if (flowplayer.support.firstframe) {
// re-sign url
fpajax();
} else {
// load static url
api.load();
}
}
});
 
} else {
api.load(clip);
 
}
}
});
 
};
 

// determine video type so we need to retrieve only 1 source via ajax
// as 3 ajax calls would be very slow and expensive
// NOTE: the /forceflash/ conditional is only for testing!
// in production just use:
// if (flowplayer.support.video) {
if (!/forceflash/.test(location.search) && flowplayer.support.video) {
$(["video/webm", "video/mp4"]).each(function (i, type) {
if (!!$("<video/>")[0].canPlayType(type).replace("no", "")) {
vtype = type;
return false;
}
});
}
 
fpajax();
 
});

JavaScript

<body/>

A HTML/CSS only splash setup using the flowplayer class, but without VIDEO tag right at page load, before player install to avoid vertical jumping due to the Ajax query delay:

<div id="player" class="flowplayer fp-slim is-closeable">
<!-- avoid vertical jump due to ajax delay -->
<div class="fp-ratio"></div>
</div>

HTML

getsignedurl.php

<?php
header('Access-Control-Allow-Origin: *');
 
$resource = $_GET['movie'];
// default expiry after 5 seconds
$expires = time() + (isset($_GET['expires']) ? $_GET['expires'] : 5);
 
// key pair generated for cloudfront
$keyPairId = 'APKAJJZ2ZVCTME3I4VSQ';
 
$json = '{"Statement":[{"Resource":"' . $resource . '","Condition":{"DateLessThan":{"AWS:EpochTime":' . $expires . '}}}]}';
 
// read cloudfront private key pair
$fp = fopen('/home/user/awskeys/pk-' . $keyPairId . '.pem', 'r');
$priv_key = fread($fp, 8192);
fclose($fp);
 
// create the private key
$key = openssl_get_privatekey($priv_key);
 
// sign the policy with the private key
// depending on your php version you might have to use
// openssl_sign($json, $signed_policy, $key, OPENSSL_ALGO_SHA1)
openssl_sign($json, $signed_policy, $key);
 
openssl_free_key($key);
 
// create url safe signed policy
$base64_signed_policy = base64_encode($signed_policy);
$signature = str_replace(array('+', '=', '/'), array('-', '_', '~'), $base64_signed_policy);
 
// construct the url
$url = $resource . '?Expires=' . $expires . '&Signature=' . $signature . '&Key-Pair-Id=' . $keyPairId;
 
echo $url;

PHP