samvera_hls 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +16 -0
  3. data/README.md +63 -0
  4. data/Rakefile +35 -0
  5. data/app/assets/javascripts/browser_notification.js +39 -0
  6. data/app/assets/javascripts/embed.js +18 -0
  7. data/app/assets/javascripts/hls_initialize.js.erb +22 -0
  8. data/app/assets/javascripts/include_player.js +17 -0
  9. data/app/assets/stylesheets/embed.css +16 -0
  10. data/app/assets/stylesheets/hydra-hls-styles.scss +20 -0
  11. data/app/assets/stylesheets/samvera-hls-styles.scss +20 -0
  12. data/app/controllers/concerns/samvera_hls/file_sets_controller_behavior.rb +43 -0
  13. data/app/models/concerns/samvera_hls/file_set_behavior.rb +149 -0
  14. data/app/services/samvera_hls/hls_output_file_service.rb +10 -0
  15. data/app/views/_playlist.html.erb +7 -0
  16. data/app/views/curation_concerns/file_sets/embed.html.erb +15 -0
  17. data/app/views/curation_concerns/file_sets/media_display/_audio_hls.html.erb +5 -0
  18. data/app/views/curation_concerns/file_sets/media_display/_video_hls.html.erb +5 -0
  19. data/app/views/file_sets/embed.html.erb +15 -0
  20. data/app/views/file_sets/media_display/_audio_hls.html.erb +5 -0
  21. data/app/views/file_sets/media_display/_video_hls.html.erb +5 -0
  22. data/app/views/hyrax/file_sets/embed.html.erb +15 -0
  23. data/app/views/hyrax/file_sets/media_display/_audio_hls.html.erb +5 -0
  24. data/app/views/hyrax/file_sets/media_display/_video_hls.html.erb +5 -0
  25. data/config/hls.yml +49 -0
  26. data/config/routes.rb +7 -0
  27. data/lib/generators/samvera_hls/install/USAGE +8 -0
  28. data/lib/generators/samvera_hls/install/install_generator.rb +32 -0
  29. data/lib/generators/samvera_hls/install/templates/config/hls.yml.example +49 -0
  30. data/lib/generators/samvera_hls/install/templates/controllers/file_sets_controller.rb +6 -0
  31. data/lib/samvera_hls/derivatives/audio_derivatives.rb +8 -0
  32. data/lib/samvera_hls/derivatives/processors/audio.rb +36 -0
  33. data/lib/samvera_hls/derivatives/processors/media.rb +28 -0
  34. data/lib/samvera_hls/derivatives/processors/video.rb +37 -0
  35. data/lib/samvera_hls/derivatives/processors.rb +7 -0
  36. data/lib/samvera_hls/derivatives/video_derivatives.rb +10 -0
  37. data/lib/samvera_hls/derivatives.rb +6 -0
  38. data/lib/samvera_hls/engine.rb +6 -0
  39. data/lib/samvera_hls/version.rb +3 -0
  40. data/lib/samvera_hls.rb +12 -0
  41. data/lib/tasks/samvera_hls_tasks.rake +4 -0
  42. data/spec/dummy/README.rdoc +28 -0
  43. data/spec/dummy/Rakefile +6 -0
  44. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  45. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  46. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  47. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  48. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/spec/dummy/bin/bundle +3 -0
  50. data/spec/dummy/bin/rails +4 -0
  51. data/spec/dummy/bin/rake +4 -0
  52. data/spec/dummy/bin/setup +29 -0
  53. data/spec/dummy/config/application.rb +32 -0
  54. data/spec/dummy/config/boot.rb +5 -0
  55. data/spec/dummy/config/database.yml +25 -0
  56. data/spec/dummy/config/environment.rb +5 -0
  57. data/spec/dummy/config/environments/development.rb +41 -0
  58. data/spec/dummy/config/environments/production.rb +79 -0
  59. data/spec/dummy/config/environments/test.rb +42 -0
  60. data/spec/dummy/config/initializers/assets.rb +11 -0
  61. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  62. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  63. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  64. data/spec/dummy/config/initializers/inflections.rb +16 -0
  65. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  66. data/spec/dummy/config/initializers/session_store.rb +3 -0
  67. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  68. data/spec/dummy/config/locales/en.yml +23 -0
  69. data/spec/dummy/config/routes.rb +56 -0
  70. data/spec/dummy/config/secrets.yml +22 -0
  71. data/spec/dummy/config.ru +4 -0
  72. data/spec/dummy/public/404.html +67 -0
  73. data/spec/dummy/public/422.html +67 -0
  74. data/spec/dummy/public/500.html +66 -0
  75. data/spec/dummy/public/favicon.ico +0 -0
  76. data/spec/test_app_templates/lib/generators/test_app_generator.rb +14 -0
  77. metadata +170 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 248a3ec8d2a9e81ce645daa12c8d3a8f71056f52
4
+ data.tar.gz: 25a3e187c1359fbe35c6ac746d8cf38f5b2e9b88
5
+ SHA512:
6
+ metadata.gz: c927805c5399255af71c80a29336654b63b25c8fec9011b18d1ed5126cd6e3c1cef31621759483560f0f17a09707392854eb9f3377c8e63a4d710874a88bba77
7
+ data.tar.gz: 81b8213a48f8c9e6bd25360de8dec1dad4909b15b4fd68ddee74dbb67fdefb700982171c505d2f79a88bbaddd92c8b4341549da4bb1ec3413ae2b0416fa33de0
data/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ ##########################################################################
2
+ #
3
+ # Copyright 2016 University of California Santa Cruz Library Digital Initiatives Department
4
+ # Additional copyright may be held by others, as reflected in the commit log
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+
2
+ ## Synopsis
3
+
4
+ This gem allows a samvera application to create hls derivative media files automatically after uploading audiovisual media files, and to display those to a user through hls adaptive streaming.
5
+
6
+ ## Motivation
7
+
8
+ Samvera is a promising digital asset management solution with a growing feature set, and we wanted to use as a streaming media service for audiovisual collections. One Samvera based application - [Avalon Media System](https://github.com/avalonmediasystem) - is designed specifically to support multimedia streaming. However, when this module was developed, Avalon was not as feature rich or as fully developed as [Sufia](https://github.com/samvera/sufia) (another Samvera "head") as an asset management system, and its developer community is relatively small. Avalon also relies on a clunky third party transcoding service (Matterhorn) and external streaming server (Red5 or Wowza), creating an unweildly and bug-prone software stack. We wanted the streaming power and flexiblity promised by Avalon, in a more flexible and feature-rich asset management system like Sufia. This gem aims to integrate all of the benefits of adaptive bitrate streaming into Sufia's existing simple, efficient system for creating multimedia derivative files.
9
+
10
+ ## Security & tokens
11
+
12
+ This software is not a DRM solution. We do not claim or intend to offer protection against authorized clients storing streamed media and then using it in undesired ways. We only seek to limit streaming from our servers to authorized clients.
13
+
14
+ Streaming media presents a challenge in balancing speed with security. Media streaming requires many requests to a server, each for a small segment of the media file being played. Each of these requests must be individually authenticated to prevent access by unauthorized users. We want to give our Rails app full control over which users are authorized to view which files - but if we load our whole Rails app for each media request, we put a big load on the server and ruin our streaming media performance. On the other hand, if we don't authenticate requests for media segments (Avalon doesn't) we risk people creating their own playlist files linking directly to those segments and bypassing our authorization system entirely.
15
+
16
+ Instead, my Rails engine uses signed tokens (using a SHA-256 hash and secret key) which grant direct access to a specific file's media segments for a specified period of time. When an authorized client requests a media segment, the token is authenticated by a lightweight external script which does not need to load the entire Rails app. If a malicious client tries to link to this segment file, the link will break quickly when the token expires.
17
+
18
+ ## Requirements
19
+
20
+ An apache server is required. This may or may not be the same server that serves your Rails app (through passenger).
21
+
22
+ ## Installation
23
+
24
+ ###Install the gem
25
+ Add the following to your gem file:
26
+
27
+ ```gem 'samvera_hls'```
28
+
29
+ then run the following from your rails app root directory:
30
+
31
+ ```
32
+ bundle install
33
+ rails generate samvera_hls:install
34
+ ```
35
+
36
+ ### Update apache config
37
+ These instructions assume you have an apache server running.
38
+ Edit the appropriate configuration file for your apache server. For my setup on CentOS 7 using passenger, I added this code to the file `/etc/httpd/conf.d/10-sufia.conf` inside the VirtualHost directive .
39
+ Add the following lines, inserting a random secret key of your own and inserting the absolute path to your rails app.
40
+ ```
41
+ SetEnv hls_token_secret "PUT_A_SECRET_KEY_HERE"
42
+ RewriteEngine on
43
+ RewriteMap hls_auth "prg:/RAILS_PATH/bin/hls_auth"
44
+ RewriteRule ^/stream/.*\.ts$ "${hls_auth:%{REQUEST_URI}}"
45
+ ```
46
+ then restart your server
47
+ ```sudo systemctl restart httpd```
48
+
49
+ ### Create link to derivative folder
50
+ It is probably possible to create this link automatically, but I have not set that up yet. You need to create this symbolic link manually. The rewrite rule you created in the last step keeps this path from being accessible publicly. Note that the apache directive FollowSymLinks must be set for this to work properly.
51
+ ```
52
+ sudo su - samverauser
53
+ ln -s /mnt/MY/DERIVATIVE/PATH /srv/MY/WEB/APP/public/stream
54
+ logout
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ When you ingest new audio or visual files, hls derivatives ought to be created automatically along with the other derivative files. View partials are included for displaying these. And "Embed" action is created for file sets, allowing an endpoint for embedding these in other websites using iframes.
60
+
61
+ ## License
62
+
63
+ [www.apache.org/licenses/LICENSE-2.0](Apache License, v2.0)
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ #require 'rdoc/task'
8
+
9
+ #RDoc::Task.new(:rdoc) do |rdoc|
10
+ # rdoc.rdoc_dir = 'rdoc'
11
+ # rdoc.title = 'HydraHls'
12
+ # rdoc.options << '--line-numbers'
13
+ # rdoc.rdoc_files.include('README.rdoc')
14
+ # rdoc.rdoc_files.include('lib/**/*.rb')
15
+ #end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
23
+
24
+ require 'rspec/core'
25
+ require 'rspec/core/rake_task'
26
+
27
+ desc "Run all specs in spec directory (excluding plugin specs)"
28
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
29
+
30
+ task :default => :spec
31
+
32
+ load 'rails/tasks/statistics.rake'
33
+
34
+ Bundler::GemHelper.install_tasks
35
+
@@ -0,0 +1,39 @@
1
+ function isChrome(nAgt) {
2
+ return nAgt.indexOf('Chrome') != -1
3
+ }
4
+
5
+ function getMacVersion(nAgt) {
6
+ var macreg = /Mac OS X/;
7
+ if (macreg.test(nAgt)) {
8
+ //it is a mac
9
+ return /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1].replace(/_/g,".");
10
+ }
11
+ return false;
12
+ }
13
+
14
+ function versionCompare(left, right) {
15
+ if (typeof left + typeof right != 'stringstring')
16
+ return false;
17
+ var a = left.split('.')
18
+ , b = right.split('.')
19
+ , i = 0, len = Math.max(a.length, b.length);
20
+
21
+ for (; i < len; i++) {
22
+ if ((a[i] && !b[i] && parseInt(a[i]) > 0) || (parseInt(a[i]) > parseInt(b[i]))) {
23
+ return true;
24
+ } else if ((b[i] && !a[i] && parseInt(b[i]) > 0) || (parseInt(a[i]) < parseInt(b[i]))) {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ return 0;
30
+ }
31
+
32
+ $(document).ready(function(){
33
+ var nAgt = navigator.userAgent;
34
+ if (isChrome(nAgt)) {
35
+ if(versionCompare("10.12",getMacVersion(nAgt)))
36
+ alert('Warning: our streaming media sometimes has problems displaying on your operating system when using the Chrome browser. If you experience problems viewing content, try using another browser such as Safari or Firefox, or update your operating system to the latest version of OS X.');
37
+ }
38
+ });
39
+
@@ -0,0 +1,18 @@
1
+ // This is a manifest file that'll be compiled into embed.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require browser_notification
14
+ //= require jquery
15
+ //= require mediaelement-and-player
16
+ //= require mep-feature-playlist
17
+ //= require hls_initialize
18
+
@@ -0,0 +1,22 @@
1
+ $(document).ready(function(){
2
+
3
+ if(typeof isPlaylist == "undefined")
4
+ isPlaylist = false;
5
+ if(isPlaylist) {
6
+ $(".hls-media").mediaelementplayer({
7
+ audioWidth: '100%',
8
+ loop: true,
9
+ shuffle: false ,
10
+ playlist: true,
11
+ playlistposition: 'bottom',
12
+ features: ['playlistfeature', 'prevtrack', 'playpause', 'nexttrack', 'loop', 'shuffle', 'playlist', 'current', 'progress', 'duration', 'volume'],
13
+ flashName: "<%= asset_path("flashmediaelement.swf") %>"
14
+ });
15
+ } else {
16
+ $(".hls-media").mediaelementplayer({
17
+ audioWidth: '100%',
18
+ flashName: "<%= asset_path("flashmediaelement.swf") %>"
19
+ });
20
+ }
21
+ });
22
+
@@ -0,0 +1,17 @@
1
+ // This is a manifest file that'll be compiled into include_player.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require browser_notification
14
+ //= require mediaelement-and-player
15
+ //= require mep-feature-playlist
16
+ //= require hls_initialize
17
+
@@ -0,0 +1,16 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into embed.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require mediaelementplayer.min
14
+ *= require mep-feature-playlist
15
+ *= require samvera-hls-styles
16
+ */
@@ -0,0 +1,20 @@
1
+ .mejs-container.hls-media.mejs-audio {
2
+ height:auto !important;
3
+ }
4
+
5
+ .mejs-container.hls-media {
6
+ clear:both;
7
+ min-height: 30px;
8
+
9
+ .mejs-layers { padding-top:2em; }
10
+
11
+ .mejs-playlist.mejs-layer {
12
+ position: relative !important;
13
+ ul.mejs { margin-top:0px !important;}
14
+ }
15
+
16
+ .mejs-controls {
17
+ bottom:auto;
18
+ top:0;
19
+ }
20
+ }
@@ -0,0 +1,20 @@
1
+ .mejs-container.hls-media.mejs-audio {
2
+ height:auto !important;
3
+ }
4
+
5
+ .mejs-container.hls-media {
6
+ clear:both;
7
+ min-height: 30px;
8
+
9
+ .mejs-layers { padding-top:2em; }
10
+
11
+ .mejs-playlist.mejs-layer {
12
+ position: relative !important;
13
+ ul.mejs { margin-top:0px !important;}
14
+ }
15
+
16
+ .mejs-controls {
17
+ bottom:auto;
18
+ top:0;
19
+ }
20
+ }
@@ -0,0 +1,43 @@
1
+ module SamveraHls
2
+ module FileSetsControllerBehavior
3
+ extend ActiveSupport::Concern
4
+
5
+ # skip_authorize_resource :only => [:show,:embed,:master,:variant]
6
+
7
+ def embed
8
+ response.headers["X-FRAME-OPTIONS"] = "ALLOWALL"
9
+ fs = FileSet.find(params[:id])
10
+ @playlist_url = fs.hls_master_url
11
+ @media_partial = media_display_partial(fs)
12
+ render layout: false
13
+ end
14
+
15
+ def master
16
+ fs = FileSet.find(params[:id])
17
+ render inline: fs.hls_master_playlist(root_url), content_type: 'application/x-mpegurl'
18
+ end
19
+
20
+ def variant
21
+ fs = FileSet.find(params[:id])
22
+ render inline: fs.hls_segment_playlist(root_url,params[:format]), content_type: 'application/x-mpegurl'
23
+ end
24
+
25
+ def media_display_partial(file_set)
26
+ 'curation_concerns/file_sets/media_display/' +
27
+ if file_set.image?
28
+ 'image'
29
+ elsif file_set.video?
30
+ 'video_hls'
31
+ elsif file_set.audio?
32
+ 'audio_hls'
33
+ elsif file_set.pdf?
34
+ 'pdf'
35
+ elsif file_set.office_document?
36
+ 'office_document'
37
+ else
38
+ 'default'
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,149 @@
1
+ require "samvera_hls/derivatives/video_derivatives"
2
+ require "samvera_hls/derivatives/audio_derivatives"
3
+
4
+ module SamveraHls
5
+ module FileSetBehavior
6
+ extend ActiveSupport::Concern
7
+
8
+ def create_derivatives(filename)
9
+ case mime_type
10
+ when *self.class.audio_mime_types
11
+ hls_dir = File.join(derivative_dir,"hls")
12
+ create_audio_derivates filename, hls_dir
13
+ when *self.class.video_mime_types
14
+ hls_dir = File.join(derivative_dir,"hls")
15
+ create_video_derivates filename, hls_dir
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def hls_master_url
22
+ File.join("file_set",id,"hls.m3u8")
23
+ end
24
+
25
+ def hls_master_playlist root_url
26
+ type = mime_type.split('/')[0]
27
+ defaults = hls_config[type]["default"]
28
+ playlist = "#EXTM3U\n"
29
+ playlist << "#EXT-X-VERSION:6\n"
30
+ variants.each{|variant|
31
+ next if variant.blank?
32
+ path, filename = File.split(variant)
33
+ format = filename.gsub(".m3u8","")
34
+ options = defaults.merge(hls_config[type][format])
35
+ playlist << "#EXT-X-STREAM-INF:PROGRAM-ID=1,"
36
+ playlist << "BANDWIDTH=#{options["maxrate"]},"
37
+ playlist << "CODECS=\"#{options["codec_code"]}\","
38
+ playlist << "RESOLUTION=#{options["resolution"]}\n"
39
+ playlist << File.join(root_url,variant_url(format)) + "\n"
40
+ }
41
+ playlist
42
+ end
43
+
44
+ def hls_segment_playlist root_url, format
45
+ playlist = ""
46
+ File.open(segment_playlist_path(format),'r') {|file|
47
+ file.each_line do |line|
48
+ if line.include? ".ts" then
49
+ playlist << File.join(root_url,
50
+ segment_url_base,
51
+ timestamp.to_s,
52
+ token(line),
53
+ line).strip+"\n"
54
+ else
55
+ playlist << line.strip+"\n"
56
+ end
57
+ end
58
+ }
59
+ playlist
60
+ end
61
+
62
+
63
+ module ClassMethods
64
+
65
+ def audio_mime_types
66
+ # audio/x-wave is the mime type that fits 0.6.0 returns for a wav file.
67
+ # audio/mpeg is the mime type that fits 0.6.0 returns for an mp3 file.
68
+ ['audio/mp3', 'audio/mpeg', 'audio/wav', 'audio/x-wave', 'audio/x-wav', 'audio/ogg', 'audio/flac','audio/x-flac', 'audio/x-aiff', 'audio/aiff', ]
69
+ end
70
+ end
71
+
72
+
73
+
74
+ private
75
+
76
+ def segment_url_base
77
+ File.join("stream",derivative_url,"hls")
78
+ end
79
+
80
+ def variants
81
+ variants = Dir.glob(File.join(derivative_dir,"hls","*.m3u8"))
82
+ variants.map!{ |file|
83
+ name = File.split(file)[1]
84
+ }
85
+ end
86
+
87
+ def variant_url format
88
+ File.join("file_set",id,format,"variant.m3u8")
89
+ end
90
+
91
+ def create_audio_derivates filename, hls_dir
92
+ outputs = [
93
+ { label: 'mp3', format: 'mp3', url: derivative_url('mp3') },
94
+ { label: 'ogg', format: 'ogg', url: derivative_url('ogg') },
95
+ { label: 'hls_hd', format: 'hls_hd',path: hls_dir},
96
+ { label: 'hls', format: 'hls', path: hls_dir}]
97
+ SamveraHls::Derivatives::AudioDerivatives.create(filename,{:outputs => outputs})
98
+ end
99
+
100
+ def create_video_derivates filename, hls_dir
101
+ outputs = [{ label: :thumbnail, format: 'jpg', url: derivative_url('thumbnail') },
102
+ { label: 'webm', format: 'webm', url: derivative_url('webm') },
103
+ { label: 'mp4', format: 'mp4', url: derivative_url('mp4') },
104
+ { label: 'hls_high', format: "hls_high", path: hls_dir},
105
+ { label: 'hls_med', format: "hls_med", path: hls_dir},
106
+ { label: 'hls_low', format: "hls_low", path: hls_dir}]
107
+ SamveraHls::Derivatives::VideoDerivatives.create(filename,{:outputs => outputs})
108
+ end
109
+
110
+ def token line
111
+ @token ||= Digest::SHA256.hexdigest("/" + File.join(segment_url_base,timestamp.to_s,line).strip[0...-9] + token_secret)
112
+ end
113
+
114
+ def timestamp
115
+ @timestamp ||= Time.now.to_i + 7200
116
+ end
117
+
118
+ def token_secret
119
+ @token_secret ||= ENV['hls_token_secret']
120
+ end
121
+
122
+ def hls_config
123
+ @hls_config ||= YAML.load_file(Rails.root.join('config','hls.yml'))
124
+ end
125
+
126
+ def segment_playlist_path format
127
+ File.join(derivative_dir,"hls",format+".m3u8")
128
+ end
129
+
130
+ def derivative_url(destination_name = nil)
131
+ if destination_name.nil?
132
+ @deriv_url ||= derivative_dir.gsub(CurationConcerns.config.derivatives_path,"")
133
+ else
134
+ path = derivative_path(destination_name)
135
+ URI("file://#{path}").to_s
136
+ end
137
+ end
138
+
139
+ def derivative_dir
140
+ File.split(derivative_path("dummy"))[0]
141
+ end
142
+
143
+ def derivative_path destination_name
144
+ CurationConcerns::DerivativePath.derivative_path_for_reference(self, destination_name)
145
+ end
146
+
147
+
148
+ end
149
+ end
@@ -0,0 +1,10 @@
1
+ require 'samvera/derivatives'
2
+ module SamveraHls
3
+ class HlsOutputFileService < CurationConcerns::PersistDerivatives
4
+ def self.call( directives,temp_dir)
5
+ hls_dir = directives[:path]
6
+ FileUtils.mkdir_p(hls_dir)
7
+ FileUtils.move(Dir.glob(temp_dir+"/*") , hls_dir)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ <audio controls="controls" class="hls-media" preload="auto">
2
+ <% file_sets.each do |file_set| %>
3
+ <source src="<%= File.join(request.base_url,"file_set",file_set.id,"hls.m3u8") %>" type="application/x-mpegurl"/>
4
+ <%end %>
5
+ Your browser does not support the audio tag.
6
+ </audio>
7
+ <script type="text/javascript">isPlaylist = true</script>
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta title="media">
8
+ <%= javascript_include_tag "embed" %>
9
+ <%= stylesheet_link_tag "embed" %>
10
+ </head>
11
+
12
+ <body>
13
+ <%= render @media_partial %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,5 @@
1
+ <audio controls="controls" class="hls-media" preload="auto" width="100%" height="100%">
2
+ <source src="<%= File.join(request.base_url,"file_set",file_set.id,"hls.m3u8") %>" type="application/x-mpegurl"/>
3
+ Your browser does not support the audio tag.
4
+ </audio>
5
+
@@ -0,0 +1,5 @@
1
+ <video class="hls-media" preload="auto" width="100%" height="100%">
2
+ <source src="<%= File.join(request.base_url,"file_set",file_set.id,"hls.m3u8") %>" type="application/x-mpegurl"/>
3
+ Your browser does not support the video tag.
4
+ </video>
5
+
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta title="media">
8
+ <%= javascript_include_tag "embed" %>
9
+ <%= stylesheet_link_tag "embed" %>
10
+ </head>
11
+
12
+ <body style="margin:0px;">
13
+ <%= render @media_partial %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,5 @@
1
+ <audio controls="controls" class="hls-media" preload="auto">
2
+ <source src="<%= File.join(request.base_url,"file_set",file_set.id,"hls.m3u8") %>" type="application/x-mpegurl"/>
3
+ Your browser does not support the audio tag.
4
+ </audio>
5
+
@@ -0,0 +1,5 @@
1
+ <video class="hls-media" preload="auto">
2
+ <source src="<%= File.join(request.base_url,"file_set",file_set.id,"hls.m3u8") %>" type="application/x-mpegurl"/>
3
+ Your browser does not support the video tag.
4
+ </video>
5
+
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta title="media">
8
+ <%= javascript_include_tag "embed" %>
9
+ <%= stylesheet_link_tag "embed" %>
10
+ </head>
11
+
12
+ <body>
13
+ <%= render @media_partial %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,5 @@
1
+ <audio controls="controls" class="hls-media" preload="auto" width="100%" height="100%">
2
+ <source src="<%= File.join(request.base_url,"file_set",file_set.id,"hls.m3u8") %>" type="application/x-mpegurl"/>
3
+ Your browser does not support the audio tag.
4
+ </audio>
5
+
@@ -0,0 +1,5 @@
1
+ <video class="hls-media" preload="auto" width="100%" height="100%">
2
+ <source src="<%= File.join(request.base_url,"file_set",file_set.id,"hls.m3u8") %>" type="application/x-mpegurl"/>
3
+ Your browser does not support the video tag.
4
+ </video>
5
+
data/config/hls.yml ADDED
@@ -0,0 +1,49 @@
1
+ ---
2
+ video:
3
+ hls_high:
4
+ bitrate: 6500000
5
+ maxrate: 6800000
6
+ vf: "scale=1280:-2"
7
+ resolution: "1280x720"
8
+ hls_med:
9
+ bitrate: 3500000
10
+ maxrate: 3600000
11
+ vf: "scale=960:-2"
12
+ resolution: "960x540"
13
+ hls_low:
14
+ r: 15
15
+ profile_v: baseline
16
+ level: 3.0
17
+ bitrate: 400000
18
+ maxrate: 420000
19
+ g: 45
20
+ segment_list_type: m3u8
21
+ vf: "scale=480:-2"
22
+ codec_code: "mp4a.40.2,avc1.42001e"
23
+ resolution: "480x360"
24
+ default:
25
+ r: 29.97
26
+ pix_fmt: yuv420p
27
+ vcodec: libx264
28
+ acodec: aac
29
+ profile_v: main
30
+ level: 3.1
31
+ codec_code: "mp4a.40.2,avc1.4d001f"
32
+ segment_time: 9
33
+ g: 90
34
+ vf: "scale=960:-2"
35
+ audio:
36
+ hls_hd:
37
+ bitrate: 128000
38
+ maxrate: 140000
39
+ hls:
40
+ bitrate: 64000
41
+ maxrate: 70000
42
+ default:
43
+ acodec: aac
44
+ flags: "-f segment -global_header"
45
+ segment_time: 9
46
+ segment_format: mpeg_ts
47
+ segment_list_type: m3u8
48
+ codec_code: "mp4a.40.2"
49
+ resolution: 300x50
data/config/routes.rb ADDED
@@ -0,0 +1,7 @@
1
+ Rails.application.routes.draw do
2
+
3
+ get '/file_set/:id/hls.m3u8', to: 'file_sets#master'
4
+ get '/file_set/:id/:format/variant.m3u8', to: 'file_sets#variant'
5
+ get '/file_set/:id/embed', to: 'file_sets#embed'
6
+
7
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Installs samvera_hls into your rails application.
3
+ * adds the routes to playlist files
4
+ * adds hls derivative file creation
5
+ * installs new viewer for adaptive streaming display
6
+
7
+ Example:
8
+ rails generate samvera_hls:install