Class: M3U8Generator

Inherits:
Object
  • Object
show all
Includes:
HLSConstants
Defined in:
lib/m3u8_generator.rb

Constant Summary

Constants included from HLSConstants

HLSConstants::EXTINF, HLSConstants::EXTM3U, HLSConstants::EXT_X_ALLOW_CACHE, HLSConstants::EXT_X_CUE_IN, HLSConstants::EXT_X_CUE_OUT, HLSConstants::EXT_X_DISCONTINUITY, HLSConstants::EXT_X_ENDLIST, HLSConstants::EXT_X_FAXS_CM, HLSConstants::EXT_X_KEY, HLSConstants::EXT_X_KEY_ATTRS, HLSConstants::EXT_X_MEDIA, HLSConstants::EXT_X_MEDIA_SEQUENCE, HLSConstants::EXT_X_PLAYLIST_TYPE, HLSConstants::EXT_X_STREAM_INF, HLSConstants::EXT_X_STREAM_INF_ATTRS, HLSConstants::EXT_X_TARGETDURATION, HLSConstants::EXT_X_VERSION, HLSConstants::FIXNUM_MAX, HLSConstants::SUPPORTED_M3U8_VERSIONS

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#is_serving_domain_provider_defaultObject

Returns the value of attribute is_serving_domain_provider_default.



27
28
29
# File 'lib/m3u8_generator.rb', line 27

def is_serving_domain_provider_default
  @is_serving_domain_provider_default
end

#m3u8_serving_domainObject

Returns the value of attribute m3u8_serving_domain.



26
27
28
# File 'lib/m3u8_generator.rb', line 26

def m3u8_serving_domain
  @m3u8_serving_domain
end

#old_serving_domainObject

Returns the value of attribute old_serving_domain.



25
26
27
# File 'lib/m3u8_generator.rb', line 25

def old_serving_domain
  @old_serving_domain
end

Instance Method Details

#append_auth_to_key_url_in_m3u8(original_m3u8_url, authentication_params = {}) ⇒ Object



157
158
159
160
161
162
163
164
165
166
# File 'lib/m3u8_generator.rb', line 157

def append_auth_to_key_url_in_m3u8(original_m3u8_url, authentication_params={})
  response = Net::HTTP.get(URI(original_m3u8_url))
  hls_manifest = HLSManifest.new(response)
  hls_manifest.line_items.each do |item|
    next unless defined?(item.key)
    key = item.key
    WarpgateUtils.append_params(key.uri, authentication_params) if key && key.uri
  end
  hls_manifest.build
end

#build_bitrate_m3u8(chunks, authentication_params = {}) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/m3u8_generator.rb', line 124

def build_bitrate_m3u8(chunks, authentication_params={})
  file_contents = ""
  max_chunk_length = 0
  chunks.each do |chunk|
    # Chunk Duration
    file_contents << "#EXTINF:#{chunk[:chunk_length]},\n"
    max_chunk_length = chunk[:chunk_length] if chunk[:chunk_length] > max_chunk_length

    # Chunk decryption key url
    if chunk[:key_url] && !chunk[:key_url].empty?
      final_auth_url = WarpgateUtils.append_params(chunk[:key_url], authentication_params)
      file_contents << "#EXT-X-KEY:METHOD=AES-128,URI=\"#{chunk[:key_url]}\"\n"
    end

    # Actual Chunk
    file_contents << "#{chunk[:chunk_url]}\n"
  end
  file_contents << "#{EXT_X_ENDLIST}\n"
  "#EXTM3U\n#EXT-X-TARGETDURATION:#{max_chunk_length}\n#EXT-X-MEDIA-SEQUENCE:0\n" + file_contents
end

#build_top_level_m3u8(embed_code, streams, min_bitrate = -1,, max_bitrate = 1000000000, target_bitrate = nil, single_bit_rate = false, authentication_params = {}, captions = nil, enable_resolution_tag = false) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/m3u8_generator.rb', line 29

def build_top_level_m3u8(embed_code,
                         streams,
                         min_bitrate=-1,
                         max_bitrate=1000000000,
                         target_bitrate=nil,
                         single_bit_rate=false,
                         authentication_params={},
                         captions=nil,
                         enable_resolution_tag=false)

  # Check the streams object to verify the right Stream attributes are passed in
  streams.each do |stream|
    stream[:average_video_bitrate]
    stream[:muxing_format]
    stream[:thrift_path]
    stream[:audio_bitrate]
    stream[:video_width]
    stream[:video_height]
    stream[:is_encrypted]
  end

  file_contents = EXTM3U + "\n"

  if captions
    captions.each do |caption|
      file_contents << "#{EXT_X_MEDIA}:TYPE=SUBTITLES,GROUP-ID=\"subs\"," +
        "NAME=\"#{caption[:language_name]}\"," +
        "DEFAULT=#{caption[:default] ? 'YES' : 'NO'}," +
        "FORCED=NO," +
        "AUTOSELECT=#{caption[:autoselect] ? 'YES' : 'NO'}," +
        "LANGUAGE=\"#{caption[:language_code]}\"," +
        "URI=\"#{caption[:url]}\"\n"
    end
  end

  # We might have different resolutions per bitrate, but when generating the top level m3u8, we
  # only care about unique bitrates.
  unique_bitrates = {}
  filtered_streams = []
  streams.each do |stream|
    next if stream[:average_video_bitrate] > max_bitrate || stream[:average_video_bitrate] < min_bitrate
    next if unique_bitrates[stream[:average_video_bitrate]]
    unique_bitrates[stream[:average_video_bitrate]] = true
    filtered_streams << stream
  end

  # After we filter out streams above max bitrate, let's shuffle the array and put the median first.
  if(target_bitrate)
    final_streams = shuffle_array_min_before_target_first(filtered_streams, target_bitrate)
  else
    final_streams = shuffle_array_with_median_first(filtered_streams)
    #if possible, do not let the first stream be audio only.
    if final_streams != nil && final_streams.length > 1 && final_streams[0][:muxing_format].upcase == 'AAC'
      final_streams[0], final_streams[1] = final_streams[1], final_streams[0]
    end
  end

  if single_bit_rate
    final_streams = final_streams[0..0]
  end

  # For Adobe Access content, we have to append a link to the key in the manifest
  streams.each do |stream|
    if stream[:muxing_format].to_s == "TS_FA" || stream[:muxing_format].to_s == "AAC_FA"
      file_contents << (EXT_X_FAXS_CM + ":URI=\"#{@m3u8_serving_domain}/#{embed_code}/#{embed_code}.drmmeta\"\n")
      break
    end
  end

  # Now build the m3u8 file.
  final_streams.each do |stream|
    total_bitrate = stream[:average_video_bitrate] + stream[:audio_bitrate]
    should_append_resolution = enable_resolution_tag && stream[:muxing_format].upcase != 'AAC'

    stream_info = "#{EXT_X_STREAM_INF}:PROGRAM-ID=1,BANDWIDTH=#{total_bitrate * 1000}"
    stream_info += ",RESOLUTION=#{stream[:video_width]}x#{stream[:video_height]}" if should_append_resolution
    stream_info += ",CODECS=\"mp4a.40.2,mp4a.40.5\"" if stream[:muxing_format].upcase == 'AAC' #add audio
    stream_info += ",SUBTITLES=\"subs\"" if captions #add captions group
    file_contents << stream_info + "\n"

    stream_url = ""
    if (@is_serving_domain_provider_default && !stream[:is_encrypted]) || authentication_params['secure_ios_token']
      stream_url = "#{@old_serving_domain}/#{embed_code}_#{stream[:average_video_bitrate]}.m3u8"
    else
      # Prepended embed code is handled by gen_serving_domain
      stream_url = "#{@m3u8_serving_domain}/#{stream[:thrift_path]}/#{stream[:average_video_bitrate]}_" +
                   "#{stream[:audio_bitrate]}_#{stream[:video_width]}_#{stream[:video_height]}.m3u8"
    end
    WarpgateUtils.append_params(stream_url, authentication_params)

    file_contents << "#{stream_url}\n"
  end
  file_contents
end

#change_sub_m3u8_inside_master_m3u8(original_m3u8_url, sub_m3u8_domain, authentication_params = {}) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
# File 'lib/m3u8_generator.rb', line 145

def change_sub_m3u8_inside_master_m3u8(original_m3u8_url, sub_m3u8_domain, authentication_params={})
  response = Net::HTTP.get(URI(original_m3u8_url))
  hls_manifest = HLSManifest.new(response)
  hls_manifest.line_items.each do |item|
    next unless defined?(item.location)
    encoded_url = Base64.encode64(item.location).gsub("\n", "")
    new_url = "#{sub_m3u8_domain}?original_url=#{encoded_url}"
    item.location = WarpgateUtils.append_params(new_url, authentication_params)
  end
  hls_manifest.build
end

#shuffle_array_min_before_target_first(array, target) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/m3u8_generator.rb', line 168

def shuffle_array_min_before_target_first(array, target)
  return array if array.nil? || array.length < 2
  #assume a sorted array, do a binary search for closest
  min = 0
  max = array.length - 1
  while min < max do
    mid = min + (max - min) / 2
    array[mid][:average_video_bitrate] < target.to_i ? min = mid + 1 : max = mid
  end
  t = array[min][:average_video_bitrate] <= target.to_i ? min : min - 1
  t = t < 0 ? 0 : t
  result = array.clone
  result.delete_at(t)
  result.unshift array[t]
  return result
end

#shuffle_array_with_median_first(array) ⇒ Object



185
186
187
188
189
190
191
192
# File 'lib/m3u8_generator.rb', line 185

def shuffle_array_with_median_first(array)
  return array if array.nil? || array.length < 3
  median = array[array.length / 2]
  result = array.clone
  result.delete_at(array.length / 2)
  result.unshift median
  return result
end