2020/lib/functional/mediaSourceSetup.js (208 lines of code) (raw):

/** * @license * Copyright 2018 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; (function() { window.hasMediaSource = function() { if (window.MediaSource != null || window.webkitMediaSource != null) return true; var v = document.createElement('video'); if (v.webkitSourceAddId == null && v.sourceAddId == null) { log('Media Source not found!'); return false; } return true; } // Converted to property of Media Source prototype. window.attachMediaSource = function(video, msrc) { msrc.attachTo(video); } var dlog = window.dlog || console.log.bind(console); var attachNative = function(video) { var URL = window.URL; video.src = URL.createObjectURL(this); } // Don't even bother executing the rest if the browser already has what we // need. if (window.MediaSource != null || window.WebKitMediaSource != null) { if (window.WebKitMediaSource != null) { window.MediaSource = window.WebKitMediaSource; window.MediaSource.prototype.version = 'MSE-live-webkit'; } else { window.MediaSource.prototype.version = 'MSE-live'; } window.MediaSource.prototype.attachTo = attachNative; return; } // Don't create the wrapper if we don't have MSE if (!hasMediaSource()) return; function MediaSource() { this.sourceBuffers = []; this.activeSourceBuffers = this.sourceBuffers; this.readyState = "closed"; this.msWrapperVideo = null; this.msWrapperDuration = NaN; this.msWrapperSourceIdCount = 1; this.msWrapperHandlers = {}; this.msWrapperAppended = false; } MediaSource.prototype.version = 'MSE-v0.5-wrapped'; MediaSource.prototype.msWrapperHandler = function(name, evt) { var handlers = this.msWrapperHandlers[name] || []; dlog(4, "In msWrapperHandler"); if (name == 'close') { this.readyState = 'closed'; this.msWrapperDuration = NaN; } else { this.readyState = name; } for (var i = 0; i < handlers.length; i++) { handlers[i].call(evt, evt); } } MediaSource.prototype.attachTo = function(video) { dlog(4, "In msWrapperAttach"); var names = ['open', 'close', 'ended']; for (var i = 0; i < names.length; i++) { var h = this.msWrapperHandler.bind(this, names[i]); v.addEventListener('webkitsource' + names[i], h); } var self = this; video.addEventListener('durationchange', function() { self.msWrapperDuration = video.duration; }); if (video.webkitMediaSourceURL != null) { video.src = video.webkitMediaSourceURL; } else { throw "Could not find mediaSourceURL"; } this.msWrapperVideo = video; } MediaSource.prototype.addSourceBuffer = function(type) { if (this.msWrapperVideo == null) throw "Unattached"; var id = '' + this.msWrapperSourceIdCount; this.msWrapperSourceIdCount += 1; if (this.msWrapperVideo.webkitSourceAddId != null) { this.msWrapperVideo.webkitSourceAddId(id, type); } else { throw "No sourceAddId"; } var buf = new SourceBuffer(this.msWrapperVideo, id); this.sourceBuffers.push(buf); return buf; } MediaSource.prototype.removeSourceBuffer = function(buf) { var v = this.msWrapperVideo; var id = buf.msWrapperSourceId; if (v.sourceRemoveId != null) { v.sourceRemoveId(id); } else if (this.webkitSourceRemoveId != null) { v.webkitSourceRemoveId(id); } else { throw "No sourceRemoveId"; } } MediaSource.prototype.endOfStream = function(opt_error) { var v = this.msWrapperVideo; // TODO: are these prefixed in M21? var err; if (opt_error == "network") { err = v.EOS_NETWORK_ERR; } else if (opt_error == "decode") { err = v.EOS_DECODE_ERR; } else if (opt_error == null) { err = v.EOS_NO_ERROR; } else { throw 'Unrecognized endOfStream error type: ' + opt_error; } if (v.webkitSourceEndOfStream != null) { v.webkitSourceEndOfStream(err); } else { throw "No sourceEndOfStream"; } } // The 'setDuration' method of the media element is an extension to the // MSE-v0.5 spec, which will be implemented on some devices. // Calling this method is defined to have the same semantics as setting // the duration property of the Media Source object in the current spec. // Getting it is undefined, although clearly here we just return the last // value that we set. Object.defineProperty( MediaSource.prototype, "duration", { get: function() { return this.msWrapperDuration; }, set: function(duration) { this.msWrapperDuration = duration; if (this.msWrapperVideo.webkitSourceSetDuration != null) { this.msWrapperVideo.webkitSourceSetDuration(duration); } else { dlog(1, 'webkitSourceSetDuration() missing (ignored)'); } } }); MediaSource.prototype.addEventListener = function(name, handler) { var re = /^(webkit)?source(open|close|ended)/; var match = re.exec(name); if (match && match[2]) { if (match[1]) { dlog(1, 'Ignoring webkit-prefixed event listens in wrapper'); return; } name = match[2]; var l = this.msWrapperHandlers[name] || []; l.push(handler); this.msWrapperHandlers[name] = l; } else { throw "Unrecognized event name: " + name; } } function SourceBuffer(video, id) { this.msWrapperVideo = video; this.msWrapperSourceId = id; this.msWrapperTimestampOffset = 0; } function FakeSourceBufferedRanges() { this.length = 0; } SourceBuffer.prototype.msWrapperGetBuffered = function() { dlog(5, "In msWrapperGetBuffered"); // Chrome 22 doesn't like calling sourceBuffered() before initialization // segment gets appended. if (!this.msWrapperAppended) return new FakeSourceBufferedRanges(); var v = this.msWrapperVideo; var id = this.msWrapperSourceId; if (v.webkitSourceBuffered != null) { return v.webkitSourceBuffered(id); } else { throw "No sourceBuffered"; } } SourceBuffer.prototype.append = function(bytes) { dlog(4, "In append"); var v = this.msWrapperVideo; var id = this.msWrapperSourceId; if (v.webkitSourceAppend != null) { v.webkitSourceAppend(id, bytes); } else { throw "No sourceAppend"; } this.msWrapperAppended = true; } SourceBuffer.prototype.abort = function() { dlog(4, "In abort"); var v = this.msWrapperVideo; var id = this.msWrapperSourceId; if (v.webkitSourceAbort != null) { v.webkitSourceAbort(id); } else { throw "No sourceAbort"; } } // The 'setTimestampOffset' method of the media element is an extension to the // MSE-v0.5 spec, which will be implemented on some devices. // Calling this method is defined to have the same semantics as setting the // timestampOffset property of the Media Source object in the current spec. Object.defineProperty( SourceBuffer.prototype, "timestampOffset", { get: function() { return this.msWrapperTimestampOffset; }, set: function(o) { this.msWrapperTimestampOffset = o; this.msWrapperVideo.webkitSourceTimestampOffset( this.msWrapperSourceId, o); } }); Object.defineProperty( SourceBuffer.prototype, "buffered", { get: SourceBuffer.prototype.msWrapperGetBuffered }); window.MediaSource = MediaSource; })();