summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJorrit Schippers2012-08-10 18:51:49 (GMT)
committer Jorrit Schippers2012-08-10 18:51:49 (GMT)
commit9d754b0ea2ad9ea78f23d1f51f4d391b098528c5 (patch)
tree04c6cf5e223eb8407127a0bedef3a95945f559e0
parent749c0075f58c3e793892dcbf237d1085a5a1a15a (diff)
Improve the HTTP Live Streaming module by allowing the m3u8 master index files to be saved to disk.
-rw-r--r--modules/videojs_hls/README.md24
-rw-r--r--modules/videojs_hls/videojs_hls.info6
-rw-r--r--modules/videojs_hls/videojs_hls.install13
-rw-r--r--modules/videojs_hls/videojs_hls.module73
-rw-r--r--modules/videojs_hls/videojs_hls.pages.inc70
5 files changed, 157 insertions, 29 deletions
diff --git a/modules/videojs_hls/README.md b/modules/videojs_hls/README.md
index d656922..7f037fc 100644
--- a/modules/videojs_hls/README.md
+++ b/modules/videojs_hls/README.md
@@ -13,9 +13,6 @@ This module intercepts m3u8 files supplied to the Video.js module.
It replaces these files with one new dynamically generated file file that makes
bandwidth switching available to iOS devices.
-This module does not provide an administrative interface and has no
-settings. It works automatically when it is enabled.
-
Requirements
------------
@@ -23,13 +20,32 @@ Requirements
is supplied there is no choice for the iOS player, so no master index is
needed.
2. The files need to have the filemime application/vnd.apple.mpegurl.
-3. The filenames need to contain `<number>k` in the filename, such as
+3. The filenames need to contain `<number>k` in the file name, such as
`sample-640k.m3u8`. This number is used to indicate the bandwidth
to the client.
You can use the Video module with the Zencoder transcoder to create files
that are compatible with the Video.js module.
+Configuration
+-------------
+
+The module works out of the box without configuration, provided you meet the
+requirements and Video.js is working correctly.
+By default, the m3u8 master index files are created dynamically: the paths
+to the individual files are embedded in the path of the index file. This works
+in most of the times and only breaks if the paths to the m3u8 files are very
+long.
+In those cases, you can change the `Delivery mode` to `Static files` in
+the Video.js settings page. Now, the m3u8 master index file will be stored on
+a configurable location. The filename will be formed by the MD5 hash of the
+file contents, so a new file will only be written if the source file names
+change. The master index file will never be deleted, so it is advisable to
+write the master index files to a dedicated directoryso they can be removed
+occasionally. It is no problem to remove a m3u8 master index file because they
+will be recreated when needed.
+
+
Also see
--------
diff --git a/modules/videojs_hls/videojs_hls.info b/modules/videojs_hls/videojs_hls.info
index 9d88d10..d6aa689 100644
--- a/modules/videojs_hls/videojs_hls.info
+++ b/modules/videojs_hls/videojs_hls.info
@@ -1,5 +1,7 @@
name = Video.js HTTP Live Streaming
-description = Extends the functionality of Video.js by combining multiple m3u8 files into one file that can be used for bandwidth switching. This module provides no administrative interface. See README.md for more information.
+description = Extends the functionality of Video.js by combining multiple m3u8 files into one file that can be used for bandwidth switching.
package = Media
core = 7.x
-dependencies[] = videojs \ No newline at end of file
+dependencies[] = videojs
+
+configure = admin/config/media/videojs \ No newline at end of file
diff --git a/modules/videojs_hls/videojs_hls.install b/modules/videojs_hls/videojs_hls.install
index eaba664..b5f5cbc 100644
--- a/modules/videojs_hls/videojs_hls.install
+++ b/modules/videojs_hls/videojs_hls.install
@@ -5,10 +5,19 @@
*/
/**
- * Implements hook_uninstall().
+ * Implements hook_install().
*
* Makes sure that this module is loaded after the Video.js module.
*/
function videojs_hls_install() {
db_update('system')->fields(array('weight' => 100))->condition('name', 'videojs_hls')->execute();
-} \ No newline at end of file
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function videojs_hls_uninstall() {
+ variable_del('videojs_hls_delivery_type');
+ variable_del('videojs_hls_delivery_static_scheme');
+ variable_del('videojs_hls_delivery_static_path');
+}
diff --git a/modules/videojs_hls/videojs_hls.module b/modules/videojs_hls/videojs_hls.module
index 6abfe03..9e72a7b 100644
--- a/modules/videojs_hls/videojs_hls.module
+++ b/modules/videojs_hls/videojs_hls.module
@@ -12,7 +12,7 @@ function videojs_hls_menu() {
$items['m3u8/%'] = array(
'title' => 'm3u8 master index',
- 'page callback' => 'videojs_hls_render',
+ 'page callback' => 'videojs_hls_render_dynamic',
'page arguments' => array(1),
'access callback' => TRUE,
'file' => 'videojs_hls.pages.inc',
@@ -50,8 +50,77 @@ function videojs_hls_preprocess_videojs(&$vars) {
return;
}
+ if (variable_get('videojs_hls_delivery_type', 'dynamic') === 'dynamic') {
+ $file = url('m3u8/' . rawurlencode(implode('|', $m3u8items)));
+ }
+ else {
+ module_load_include('pages.inc', 'videojs_hls');
+ $file = videojs_hls_create_static_file($m3u8items);
+ }
+
array_unshift($vars['items'], array(
- 'uri' => url('m3u8/' . rawurlencode(implode('|', $m3u8items))),
+ 'uri' => $file,
'videotype' => 'application/vnd.apple.mpegurl',
));
}
+
+function videojs_hls_form_alter(&$form, &$form_state, $form_id) {
+ if ($form_id != 'videojs_settings_form') {
+ return;
+ }
+
+ array_unshift($form['#submit'], 'videojs_hls_submit');
+
+ $form['hls'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Video.js HTTP Live Streaming'),
+ );
+
+ $form['hls']['videojs_hls_delivery_type'] = array(
+ '#type' => 'radios',
+ '#title' => t('Delivery mode'),
+ '#options' => array(
+ 'dynamic' => t('Dynamic files: m3u8 index files are create dynamically'),
+ 'static' => t('Static files: m3u8 index files are written to disk'),
+ ),
+ '#default_value' => variable_get('videojs_hls_delivery_type', 'dynamic'),
+ '#required' => TRUE,
+ );
+
+ $scheme_options = array();
+ foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
+ $scheme_options[$scheme] = $stream_wrapper['name'];
+ }
+
+ $form['hls']['videojs_hls_delivery_static_scheme'] = array(
+ '#type' => 'radios',
+ '#title' => t('Destination location'),
+ '#options' => $scheme_options,
+ '#default_value' => variable_get('videojs_hls_delivery_static_scheme', variable_get('file_default_scheme', 'public')),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name=videojs_hls_delivery_type]' => array('value' => 'static'),
+ ),
+ ),
+ );
+
+ $form['hls']['videojs_hls_delivery_static_path'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Destination path'),
+ '#default_value' => variable_get('videojs_hls_delivery_static_path', 'm3u8'),
+ '#states' => array(
+ 'visible' => array(
+ ':input[name=videojs_hls_delivery_type]' => array('value' => 'static'),
+ ),
+ ),
+ );
+}
+
+function videojs_hls_submit($form, &$form_state) {
+ if ($form_state['values']['videojs_hls_delivery_type'] != 'static') {
+ db_delete('variable')->condition('name', array('videojs_hls_delivery_static_scheme', 'videojs_hls_delivery_static_path'), 'IN')->execute();
+
+ unset($form_state['values']['videojs_hls_delivery_static_scheme']);
+ unset($form_state['values']['videojs_hls_delivery_static_path']);
+ }
+}
diff --git a/modules/videojs_hls/videojs_hls.pages.inc b/modules/videojs_hls/videojs_hls.pages.inc
index 7640820..ed4f741 100644
--- a/modules/videojs_hls/videojs_hls.pages.inc
+++ b/modules/videojs_hls/videojs_hls.pages.inc
@@ -7,36 +7,21 @@
/**
* Menu callback for the m3u8/% path.
*/
-function videojs_hls_render($filedata) {
+function videojs_hls_render_dynamic($filedata) {
if (empty($filedata)) {
drupal_not_found();
exit;
}
$files = explode('|', rawurldecode($filedata));
+ $content = videojs_hls_create_index($files);
- $return = array();
- $matches = array();
-
- foreach ($files as $file) {
- if (!preg_match('#(\d+)k#i', $file, $matches)) {
- continue;
- }
- $bw = ($matches[1] * 1000);
- $return[$bw] = '#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=' . $bw . "\n" . file_create_url($file);
- }
-
- if (empty($return)) {
+ if (!$content) {
drupal_not_found();
exit;
}
- // Sort the files by bandwidth, lowest first.
- ksort($return);
-
- array_unshift($return, '#EXTM3U');
-
- return implode("\n", $return);
+ return $content;
}
/**
@@ -51,3 +36,50 @@ function videojs_hls_deliver($page_callback_result) {
drupal_add_http_header('Content-Type', 'application/vnd.apple.mpegurl; charset=utf-8');
echo $page_callback_result;
}
+
+function videojs_hls_create_static_file(array $m3u8items) {
+ $content = videojs_hls_create_index($m3u8items);
+
+ if (!$content) {
+ return FALSE;
+ }
+
+ $dir = variable_get('videojs_hls_delivery_static_scheme', variable_get('file_default_scheme', 'public')) . '://' . variable_get('videojs_hls_delivery_static_path', 'm3u8');
+ $dir = rtrim($dir, '/');
+ if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ watchdog('videojs_hls', 'Directory %directory is not writable, can\'t use static files. Please check your Video.js HLS settings.', array('%directory' => $dir));
+ return FALSE;
+ }
+
+ $path = $dir . '/' . md5($content) . '.m3u8';
+
+ if (!file_exists($path)) {
+ file_put_contents($path, $content);
+ }
+
+ return $path;
+}
+
+function videojs_hls_create_index(array $m3u8items) {
+ $return = array();
+ $matches = array();
+
+ foreach ($m3u8items as $m3u8item) {
+ if (!preg_match('#(\d+)k#i', $m3u8item, $matches)) {
+ continue;
+ }
+ $bw = ($matches[1] * 1000);
+ $return[$bw] = '#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=' . $bw . "\n" . file_create_url($m3u8item);
+ }
+
+ if (empty($return)) {
+ return FALSE;
+ }
+
+ // Sort the files by bandwidth, lowest first.
+ ksort($return);
+
+ array_unshift($return, '#EXTM3U');
+
+ return implode("\n", $return);
+}