AWS signed URLs on demand · Flowplayer
Now playing:
resolved video.url will be displayed here
Flowplayer requires a valid video URL at load time. However, the AWS signed URL must be
generated and is not available in time. As Github pages do not allow PHP the delay is even
more pronunced as we have to make an Ajax call to a remote origin. Therefore the player must
be install asynchronously.
To highlight the challenge we install Flowplayer in a splash setup . As we cannot install the player on page
load we mimick Flowplayer's CSS directives to implement a placeholder which deals with
flexible dimensions as
Flowplayer does with the player container.
The player is configured to go back into splash state on finish. This time the clip must be
loaded asynchronously with a fresh signed URL as the URLs returned have an expiry time of only
5 seconds. We disable the API in splash state to make a click on the player area retrieve the
new URL first.
To save time consuming Ajax calls we also mimick Flowplayer's video format picking.
<head/>
<style>
/* normal splash preparation */
#player {
background : #777 url(http://flowplayer.org/media/img/demos/minimalist.jpg) no-repeat ;
background-size : contain ;
}
/* placeholder splash */
#player .splash {
position : relative ;
width : 100% ;
cursor : pointer ;
}
/* placeholder ratio */
#player .splash .ratio {
/* aspect ratio 12/5 -> placeholder ratio 5/12 */
padding-top : 41.67%
}
/* placeholder play button */
#player .splash .playbutton {
position : absolute ;
top : 0 ;
left : 0 ;
width : 100% ;
height : 100% ;
background : url(//releases.flowplayer.org/5.3.2/skin/img/play_white.png) center no-repeat ;
background-size : 12% ;
}
<script>
// global configuration
flowplayer . conf = {
splash : true ,
ratio : 5 / 12
};
// global api setup
flowplayer ( function ( api ) {
api . bind ( "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 );
}). bind ( "finish" , function ( e , api ) {
// before going into splash state
// prevent click on container from reloading same url immediately
api . disable ( true );
api . unload ();
});
});
$ ( function () {
var video = $ ( "<video/>" ),
globaltype = "flash" , // fallback to flash
globalapi ;
// determine video type so we need to retrieve only 1 source via ajax
// as 3 ajax calls would be very slow and expensive
if ( flowplayer . support . video ) {
$ ([ "mp4" , "webm" , "ogg" ]). each ( function ( i , type ) {
if ( !! video [ 0 ]. canPlayType ( "video/" + type ). replace ( "no" , "" )) {
globaltype = type ;
return false ;
}
});
}
$ ( "#player" ). click ( function () {
// retrieve url only in (placeholder) splash state
if ( globalapi === undefined || globalapi . splash ) {
if ( globalapi === undefined ) {
// fade out placeholder play button to bridge first load period
$ ( "#player .playbutton" ). fadeOut ( 500 );
}
$ . ajax ({
// github does not offer php, therefore remote origin
url : "http://flowplayer.blacktrash.org/getsignedurl.php" ,
data : {
"movie" : "bauhaus/624x260." + ( globaltype !== "flash" ?
( globaltype !== "ogg" ? globaltype : "ogv" ) : "mp4" )
},
dataType : "text" ,
success : function ( data ) {
if ( globalapi === undefined ) {
// insert video tag with the src
video . append ( $ ( "<source/>" ). attr ({
type : "video/" + globaltype ,
src : data
}));
// replace placeholder with video tag
$ ( "#player .playbutton, #player .ratio" ). replaceWith ( video );
// install the player
$ ( "#player" ). removeClass ( "splash" ). flowplayer ();
// grab global api handle
globalapi = $ ( "#player" ). data ( "flowplayer" );
globalapi . load ();
} else {
// player is already installed, only load clip with new url
globalapi . disable ( false );
// circumvent issue #172:
// load() does not handle a string argument containing a query
// switch needed as variable values cannot be used as properties
switch ( globaltype ) {
case "webm" :
globalapi . load ([{ webm : data }]);
break ;
case "ogg" :
globalapi . load ([{ ogg : data }]);
break ;
case "flash" :
globalapi . load ([{ flash : data }]);
break ;
default :
globalapi . load ([{ mp4 : data }]);
}
}
}
});
}
});
});
<body/>
<div id= "player" class= "splash" >
<div class= "ratio" ></div>
<div class= "playbutton" ></div>
</div>
getsignedurl.php
<?php
header ( 'Access-Control-Allow-Origin: *' );
$resource = 'http://d2yz3vc7rxs49u.cloudfront.net/' . $_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 ;
?>