├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── dist └── jswebrtc.min.js ├── examples ├── player-via-html.html └── player-via-js.html ├── package-lock.json ├── package.json └── src ├── jswebrtc.js ├── player.js └── video-element.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2020 Derek Chan 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSWebrtc – 支持 SRS 的 Webrtc 播放器 2 | 3 | JSWebrtc 对浏览器的 Webrtc 做了简单的封装,支持 [SRS](https://github.com/ossrs/srs) 的 RTC 流的播放. 4 | 5 | 快速上手: 6 | ```html 7 | 8 |
9 | ``` 10 | 11 | 具体示例: [examples](/examples) 12 | 13 | 14 | ## 用法 15 | 16 | JSWebrtc 播放器可以通过 HTML 创建,只需给指定元素添加 CSS 样式 `jswebrtc` 即可: 17 | 18 | ```html 19 |
20 | ``` 21 | 22 | 也可以通过在 JavaScript 中调用 `JSWebrtc.Player()` 构造方法来创建: 23 | 24 | ```javascript 25 | var player = new JSWebrtc.Player(url [, options]); 26 | ``` 27 | 28 | 参数 `url` 是一个 webrtc 开头的地址 (webrtc://...). 29 | 30 | 参数 `options` 支持下列的配置项: 31 | 32 | - `video` – 用于播放视频的 HTML Video 元素. 33 | - `autoplay` - 是否自动播放. 默认 `false`. 34 | - `onPlay(player)` – 播放后回调 35 | - `onPause(player)` – 暂停后回调 36 | 37 | 38 | ## JSWebrtc.Player API 39 | 40 | 实例 `JSWebrtc.Player` 支持以下方法和属性: 41 | 42 | - `.play()` – 开始 43 | - `.pause()` – 暂停 44 | - `.stop()` – 停止 45 | - `.destroy()` – 停止播放并清理相关的播放资源. 46 | - `.paused` – 只读, 是否暂停播放 47 | 48 | 49 | ## 构建 50 | 51 | 如何构建 min 文件: 52 | 53 | ```sh 54 | npm install 55 | ./build.sh 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Concat all .js sources 4 | cat \ 5 | src/jswebrtc.js \ 6 | src/video-element.js \ 7 | src/player.js \ 8 | > jswebrtc.js 9 | 10 | # Minify 11 | uglifyjs jswebrtc.js -o dist/jswebrtc.min.js 12 | 13 | # Cleanup 14 | rm jswebrtc.js 15 | 16 | -------------------------------------------------------------------------------- /dist/jswebrtc.min.js: -------------------------------------------------------------------------------- 1 | var JSWebrtc={Player:null,VideoElement:null,CreateVideoElements:function(){var elements=document.querySelectorAll(".jswebrtc");for(var i=0;i=0)query_string=query_string.split("?")[1];var queries=query_string.split("&");for(var i=0;i=0){var params=app.substr(app.indexOf("?"));app=app.substr(0,app.indexOf("?"));if(params.indexOf("vhost=")>0){vhost=params.substr(params.indexOf("vhost=")+"vhost=".length);if(vhost.indexOf("&")>0){vhost=vhost.substr(0,vhost.indexOf("&"))}}}if(a.hostname==vhost){var re=/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;if(re.test(a.hostname))vhost="__defaultVhost__"}var schema="rtmp";if(rtmp_url.indexOf("://")>0)schema=rtmp_url.substr(0,rtmp_url.indexOf("://"));var port=a.port;if(!port){if(schema==="http"){port=80}else if(schema==="https"){port=443}else if(schema==="rtmp"){port=1935}else if(schema==="webrtc"||schema==="rtc"){port=1985}}var ret={url:rtmp_url,schema:schema,server:a.hostname,port:port,vhost:vhost,app:app,stream:stream};JSWebrtc.FillQuery(a.search,ret);return ret},HttpPost:function(url,data){return new Promise(function(resolve,reject){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){if(xhr.readyState===4&&(xhr.status>=200&&xhr.status<300)){var respone=JSON.parse(xhr.responseText);xhr.onreadystatechange=new Function;xhr=null;resolve(respone)}};xhr.open("POST",url,true);xhr.timeout=5e3;xhr.responseType="text";xhr.setRequestHeader("Content-Type","application/json");xhr.send(data)})}};if(document.readyState==="complete"){JSWebrtc.CreateVideoElements()}else{document.addEventListener("DOMContentLoaded",JSWebrtc.CreateVideoElements)}JSWebrtc.VideoElement=function(){"use strict";var VideoElement=function(element){var url=element.dataset.url;if(!url){throw"VideoElement has no `data-url` attribute"}var addStyles=function(element,styles){for(var name in styles){element.style[name]=styles[name]}};this.container=element;addStyles(this.container,{display:"inline-block",position:"relative",minWidth:"80px",minHeight:"80px"});this.video=document.createElement("video");this.video.width=960;this.video.height=540;addStyles(this.video,{display:"block",width:"100%"});this.container.appendChild(this.video);this.playButton=document.createElement("div");this.playButton.innerHTML=VideoElement.PLAY_BUTTON;addStyles(this.playButton,{zIndex:2,position:"absolute",top:"0",bottom:"0",left:"0",right:"0",maxWidth:"75px",maxHeight:"75px",margin:"auto",opacity:"0.7",cursor:"pointer"});this.container.appendChild(this.playButton);var options={video:this.video};for(var option in element.dataset){try{options[option]=JSON.parse(element.dataset[option])}catch(err){options[option]=element.dataset[option]}}this.player=new JSWebrtc.Player(url,options);element.playerInstance=this.player;if(options.poster&&!options.autoplay){options.decodeFirstFrame=false;this.poster=new Image;this.poster.src=options.poster;this.poster.addEventListener("load",this.posterLoaded);addStyles(this.poster,{display:"block",zIndex:1,position:"absolute",top:0,left:0,bottom:0,right:0});this.container.appendChild(this.poster)}if(!this.player.options.streaming){this.container.addEventListener("click",this.onClick.bind(this))}if(options.autoplay){this.playButton.style.display="none"}if(this.player.audioOut&&!this.player.audioOut.unlocked){var unlockAudioElement=this.container;if(options.autoplay){this.unmuteButton=document.createElement("div");this.unmuteButton.innerHTML=VideoElement.UNMUTE_BUTTON;addStyles(this.unmuteButton,{zIndex:2,position:"absolute",bottom:"10px",right:"20px",width:"75px",height:"75px",margin:"auto",opacity:"0.7",cursor:"pointer"});this.container.appendChild(this.unmuteButton);unlockAudioElement=this.unmuteButton}this.unlockAudioBound=this.onUnlockAudio.bind(this,unlockAudioElement);unlockAudioElement.addEventListener("touchstart",this.unlockAudioBound,false);unlockAudioElement.addEventListener("click",this.unlockAudioBound,true)}};VideoElement.prototype.onUnlockAudio=function(element,ev){if(this.unmuteButton){ev.preventDefault();ev.stopPropagation()}this.player.audioOut.unlock(function(){if(this.unmuteButton){this.unmuteButton.style.display="none"}element.removeEventListener("touchstart",this.unlockAudioBound);element.removeEventListener("click",this.unlockAudioBound)}.bind(this))};VideoElement.prototype.onClick=function(ev){if(this.player.isPlaying){this.player.pause();this.playButton.style.display="block"}else{this.player.play();this.playButton.style.display="none";if(this.poster){this.poster.style.display="none"}}};VideoElement.PLAY_BUTTON=''+''+''+"";VideoElement.UNMUTE_BUTTON=''+''+''+''+''+""+"";return VideoElement}();JSWebrtc.Player=function(){"use strict";var Player=function(url,options){this.options=options||{};if(!url.match(/^webrtc?:\/\//)){throw"JSWebrtc just work with webrtc"}if(!this.options.video){throw"VideoElement is null"}this.urlParams=JSWebrtc.ParseUrl(url);this.pc=null;this.autoplay=!!options.autoplay||false;this.paused=true;if(this.autoplay)this.options.video.muted=true;this.startLoading()};Player.prototype.startLoading=function(){var _self=this;if(_self.pc){_self.pc.close()}_self.pc=new RTCPeerConnection(null);_self.pc.ontrack=function(event){_self.options.video["srcObject"]=event.streams[0]};_self.pc.addTransceiver("audio",{direction:"recvonly"});_self.pc.addTransceiver("video",{direction:"recvonly"});_self.pc.createOffer().then(function(offer){return _self.pc.setLocalDescription(offer).then(function(){return offer})}).then(function(offer){return new Promise(function(resolve,reject){var port=_self.urlParams.port||1985;var api=_self.urlParams.user_query.play||"/rtc/v1/play/";if(api.lastIndexOf("/")!=api.length-1){api+="/"}var url="http://"+_self.urlParams.server+":"+port+api;for(var key in _self.urlParams.user_query){if(key!="api"&&key!="play"){url+="&"+key+"="+_self.urlParams.user_query[key]}}var data={api:url,streamurl:_self.urlParams.url,clientip:null,sdp:offer.sdp};console.log("offer: "+JSON.stringify(data));JSWebrtc.HttpPost(url,JSON.stringify(data)).then(function(res){console.log("answer: "+JSON.stringify(res));resolve(res.sdp)},function(rej){reject(rej)})})}).then(function(answer){return _self.pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:answer}))}).catch(function(reason){throw reason});if(this.autoplay){this.play()}};Player.prototype.play=function(ev){if(this.animationId){return}this.animationId=requestAnimationFrame(this.update.bind(this));this.paused=false};Player.prototype.pause=function(ev){if(this.paused){return}cancelAnimationFrame(this.animationId);this.animationId=null;this.isPlaying=false;this.paused=true;this.options.video.pause();if(this.options.onPause){this.options.onPause(this)}};Player.prototype.stop=function(ev){this.pause()};Player.prototype.destroy=function(){this.pause();this.pc&&this.pc.close()&&this.pc.destroy();this.audioOut&&this.audioOut.destroy()};Player.prototype.update=function(){this.animationId=requestAnimationFrame(this.update.bind(this));if(this.options.video.readyState<4){return}if(!this.isPlaying){this.isPlaying=true;this.options.video.play();if(this.options.onPlay){this.options.onPlay(this)}}};return Player}(); -------------------------------------------------------------------------------- /examples/player-via-html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTCPlayer 7 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/player-via-js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebRTCPlayer 7 | 8 | 9 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jswebrtc", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "uglifyjs": { 8 | "version": "2.4.11", 9 | "resolved": "https://registry.npmjs.org/uglifyjs/-/uglifyjs-2.4.11.tgz", 10 | "integrity": "sha1-NEDWTgRXWViVJEGOtkHGi7kNET4=", 11 | "dev": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jswebrtc", 3 | "version": "1.0.0", 4 | "description": "SRS webrtc player", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "webrtc", 11 | "player" 12 | ], 13 | "author": "Derek Chan", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "uglifyjs": "^2.4.11" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/jswebrtc.js: -------------------------------------------------------------------------------- 1 | /*! jswebrtc v1.0 | (c) Derek Chan | MIT license */ 2 | 3 | 4 | // This sets up the JSWebrtc "Namespace". The object is empty apart from the Now() 5 | // utility function and the automatic CreateVideoElements() after DOMReady. 6 | var JSWebrtc = { 7 | 8 | // The Player sets up the connections between source, demuxer, decoders, 9 | // renderer and audio output. It ties everything together, is responsible 10 | // of scheduling decoding and provides some convenience methods for 11 | // external users. 12 | Player: null, 13 | 14 | // A Video Element wraps the Player, shows HTML controls to start/pause 15 | // the video and handles Audio unlocking on iOS. VideoElements can be 16 | // created directly in HTML using the
tag. 17 | VideoElement: null, 18 | 19 | CreateVideoElements: function () { 20 | var elements = document.querySelectorAll('.jswebrtc'); 21 | for (var i = 0; i < elements.length; i++) { 22 | new JSWebrtc.VideoElement(elements[i]); 23 | } 24 | }, 25 | 26 | FillQuery: function (query_string, obj) { 27 | // pure user query object. 28 | obj.user_query = {}; 29 | 30 | if (query_string.length == 0) 31 | return; 32 | 33 | // split again for angularjs. 34 | if (query_string.indexOf("?") >= 0) 35 | query_string = query_string.split("?")[1]; 36 | 37 | var queries = query_string.split("&"); 38 | for (var i = 0; i < queries.length; i++) { 39 | var query = queries[i].split("="); 40 | obj[query[0]] = query[1]; 41 | obj.user_query[query[0]] = query[1]; 42 | } 43 | 44 | // alias domain for vhost. 45 | if (obj.domain) 46 | obj.vhost = obj.domain; 47 | }, 48 | 49 | ParseUrl: function (rtmp_url) { 50 | // @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri 51 | var a = document.createElement("a"); 52 | a.href = rtmp_url.replace("rtmp://", "http://") 53 | .replace("webrtc://", "http://") 54 | .replace("rtc://", "http://"); 55 | 56 | var vhost = a.hostname; 57 | var app = a.pathname.substr(1, a.pathname.lastIndexOf("/") - 1); 58 | var stream = a.pathname.substr(a.pathname.lastIndexOf("/") + 1); 59 | 60 | // parse the vhost in the params of app, that srs supports. 61 | app = app.replace("...vhost...", "?vhost="); 62 | if (app.indexOf("?") >= 0) { 63 | var params = app.substr(app.indexOf("?")); 64 | app = app.substr(0, app.indexOf("?")); 65 | 66 | if (params.indexOf("vhost=") > 0) { 67 | vhost = params.substr(params.indexOf("vhost=") + "vhost=".length); 68 | if (vhost.indexOf("&") > 0) { 69 | vhost = vhost.substr(0, vhost.indexOf("&")); 70 | } 71 | } 72 | } 73 | 74 | // when vhost equals to server, and server is ip, 75 | // the vhost is __defaultVhost__ 76 | if (a.hostname == vhost) { 77 | var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; 78 | if (re.test(a.hostname)) 79 | vhost = "__defaultVhost__"; 80 | } 81 | 82 | // parse the schema 83 | var schema = "rtmp"; 84 | if (rtmp_url.indexOf("://") > 0) 85 | schema = rtmp_url.substr(0, rtmp_url.indexOf("://")); 86 | 87 | var port = a.port; 88 | if (!port) { 89 | if (schema === 'http') { 90 | port = 80; 91 | } else if (schema === 'https') { 92 | port = 443; 93 | } else if (schema === 'rtmp') { 94 | port = 1935; 95 | } else if (schema === 'webrtc' || schema === 'rtc') { 96 | port = 1985; 97 | } 98 | } 99 | 100 | var ret = { 101 | url: rtmp_url, 102 | schema: schema, 103 | server: a.hostname, port: port, 104 | vhost: vhost, app: app, stream: stream 105 | }; 106 | 107 | JSWebrtc.FillQuery(a.search, ret); 108 | 109 | return ret; 110 | }, 111 | 112 | HttpPost: function (url, data) { 113 | return new Promise(function (resolve, reject) { 114 | var xhr = new XMLHttpRequest(); 115 | xhr.onreadystatechange = function () { 116 | if (xhr.readyState === 4 && (xhr.status >= 200 && xhr.status < 300)) { 117 | var respone = JSON.parse(xhr.responseText); 118 | xhr.onreadystatechange = new Function; 119 | xhr = null; 120 | resolve(respone); 121 | } 122 | }; 123 | 124 | xhr.open("POST", url, true); 125 | 126 | // note: In Internet Explorer, the timeout property may be set only after calling the open() 127 | // method and before calling the send() method. 128 | xhr.timeout = 5000;// 5 seconds for timeout 129 | xhr.responseType = "text"; 130 | xhr.setRequestHeader("Content-Type", "application/json"); 131 | xhr.send(data); 132 | }) 133 | 134 | } 135 | }; 136 | 137 | // Automatically create players for all found
elements. 138 | if (document.readyState === 'complete') { 139 | JSWebrtc.CreateVideoElements(); 140 | } 141 | else { 142 | document.addEventListener('DOMContentLoaded', JSWebrtc.CreateVideoElements); 143 | } 144 | -------------------------------------------------------------------------------- /src/player.js: -------------------------------------------------------------------------------- 1 | 2 | JSWebrtc.Player = (function () { 3 | "use strict"; 4 | 5 | var Player = function (url, options) { 6 | this.options = options || {}; 7 | 8 | if (!url.match(/^webrtc?:\/\//)) { 9 | throw ("JSWebrtc just work with webrtc"); 10 | } 11 | 12 | if (!this.options.video) { 13 | throw ("VideoElement is null"); 14 | } 15 | 16 | this.urlParams = JSWebrtc.ParseUrl(url); 17 | 18 | this.pc = null; 19 | this.autoplay = !!options.autoplay || false; 20 | this.paused = true; 21 | 22 | // set muted for autoplay 23 | if (this.autoplay) 24 | this.options.video.muted = true; 25 | 26 | this.startLoading(); 27 | }; 28 | 29 | Player.prototype.startLoading = function () { 30 | var _self = this; 31 | if (_self.pc) { 32 | _self.pc.close(); 33 | } 34 | 35 | _self.pc = new RTCPeerConnection(null); 36 | _self.pc.ontrack = function (event) { 37 | _self.options.video['srcObject'] = event.streams[0]; 38 | }; 39 | _self.pc.addTransceiver("audio", { direction: "recvonly" }); 40 | _self.pc.addTransceiver("video", { direction: "recvonly" }); 41 | 42 | _self.pc.createOffer().then(function (offer) { 43 | return _self.pc.setLocalDescription(offer).then(function () { return offer; }); 44 | }).then(function (offer) { 45 | return new Promise(function (resolve, reject) { 46 | var port = _self.urlParams.port || 1985; 47 | 48 | // @see https://github.com/rtcdn/rtcdn-draft 49 | var api = _self.urlParams.user_query.play || '/rtc/v1/play/'; 50 | if (api.lastIndexOf('/') != api.length - 1) { 51 | api += '/'; 52 | } 53 | 54 | var url = 'http://' + _self.urlParams.server + ':' + port + api; 55 | for (var key in _self.urlParams.user_query) { 56 | if (key != 'api' && key != 'play') { 57 | url += '&' + key + '=' + _self.urlParams.user_query[key]; 58 | } 59 | } 60 | 61 | // @see https://github.com/rtcdn/rtcdn-draft 62 | var data = { 63 | api: url, streamurl: _self.urlParams.url, clientip: null, sdp: offer.sdp 64 | }; 65 | console.log("offer: " + JSON.stringify(data)); 66 | 67 | JSWebrtc.HttpPost(url, JSON.stringify(data)).then(function (res) { 68 | console.log("answer: " + JSON.stringify(res)); 69 | resolve(res.sdp); 70 | }, function (rej) { 71 | reject(rej); 72 | }) 73 | }); 74 | }).then(function (answer) { 75 | return _self.pc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: answer })); 76 | }).catch(function (reason) { 77 | throw reason; 78 | }); 79 | 80 | if (this.autoplay) { 81 | this.play(); 82 | } 83 | }; 84 | 85 | Player.prototype.play = function (ev) { 86 | if (this.animationId) { 87 | return; 88 | } 89 | 90 | this.animationId = requestAnimationFrame(this.update.bind(this)); 91 | this.paused = false; 92 | }; 93 | 94 | Player.prototype.pause = function (ev) { 95 | if (this.paused) { 96 | return; 97 | } 98 | 99 | cancelAnimationFrame(this.animationId); 100 | this.animationId = null; 101 | this.isPlaying = false; 102 | this.paused = true; 103 | 104 | this.options.video.pause(); 105 | 106 | if (this.options.onPause) { 107 | this.options.onPause(this); 108 | } 109 | }; 110 | 111 | Player.prototype.stop = function (ev) { 112 | this.pause(); 113 | }; 114 | 115 | Player.prototype.destroy = function () { 116 | this.pause(); 117 | this.pc && this.pc.close() && this.pc.destroy(); 118 | this.audioOut && this.audioOut.destroy(); 119 | }; 120 | 121 | Player.prototype.update = function () { 122 | this.animationId = requestAnimationFrame(this.update.bind(this)); 123 | 124 | if (this.options.video.readyState < 4) { 125 | return; 126 | } 127 | 128 | if (!this.isPlaying) { 129 | this.isPlaying = true; 130 | 131 | this.options.video.play(); 132 | if (this.options.onPlay) { 133 | this.options.onPlay(this); 134 | } 135 | } 136 | }; 137 | 138 | 139 | return Player; 140 | 141 | })(); -------------------------------------------------------------------------------- /src/video-element.js: -------------------------------------------------------------------------------- 1 | 2 | JSWebrtc.VideoElement = (function () { 3 | "use strict"; 4 | 5 | var VideoElement = function (element) { 6 | var url = element.dataset.url; 7 | 8 | if (!url) { 9 | throw ("VideoElement has no `data-url` attribute"); 10 | } 11 | 12 | // Setup the div container, video and play button 13 | var addStyles = function (element, styles) { 14 | for (var name in styles) { 15 | element.style[name] = styles[name]; 16 | } 17 | }; 18 | 19 | this.container = element; 20 | addStyles(this.container, { 21 | display: 'inline-block', 22 | position: 'relative', 23 | minWidth: '80px', minHeight: '80px' 24 | }); 25 | 26 | this.video = document.createElement('video'); 27 | this.video.width = 960; 28 | this.video.height = 540; 29 | addStyles(this.video, { 30 | display: 'block', 31 | width: '100%' 32 | }); 33 | this.container.appendChild(this.video); 34 | 35 | this.playButton = document.createElement('div'); 36 | this.playButton.innerHTML = VideoElement.PLAY_BUTTON; 37 | addStyles(this.playButton, { 38 | zIndex: 2, position: 'absolute', 39 | top: '0', bottom: '0', left: '0', right: '0', 40 | maxWidth: '75px', maxHeight: '75px', 41 | margin: 'auto', 42 | opacity: '0.7', 43 | cursor: 'pointer' 44 | }); 45 | this.container.appendChild(this.playButton); 46 | 47 | // Parse the data-options - we try to decode the values as json. This way 48 | // we can get proper boolean and number values. If JSON.parse() fails, 49 | // treat it as a string. 50 | var options = { video: this.video }; 51 | for (var option in element.dataset) { 52 | try { 53 | options[option] = JSON.parse(element.dataset[option]); 54 | } 55 | catch (err) { 56 | options[option] = element.dataset[option]; 57 | } 58 | } 59 | 60 | // Create the player instance 61 | this.player = new JSWebrtc.Player(url, options); 62 | element.playerInstance = this.player; 63 | 64 | // Setup the poster element, if any 65 | if (options.poster && !options.autoplay) { 66 | options.decodeFirstFrame = false; 67 | this.poster = new Image(); 68 | this.poster.src = options.poster; 69 | this.poster.addEventListener('load', this.posterLoaded) 70 | addStyles(this.poster, { 71 | display: 'block', zIndex: 1, position: 'absolute', 72 | top: 0, left: 0, bottom: 0, right: 0 73 | }); 74 | this.container.appendChild(this.poster); 75 | } 76 | 77 | // Add the click handler if this video is pausable 78 | if (!this.player.options.streaming) { 79 | this.container.addEventListener('click', this.onClick.bind(this)); 80 | } 81 | 82 | // Hide the play button if this video immediately begins playing 83 | if (options.autoplay) { 84 | this.playButton.style.display = 'none'; 85 | } 86 | 87 | // Set up the unlock audio buton for iOS devices. iOS only allows us to 88 | // play audio after a user action has initiated playing. For autoplay or 89 | // streaming players we set up a muted speaker icon as the button. For all 90 | // others, we can simply use the play button. 91 | if (this.player.audioOut && !this.player.audioOut.unlocked) { 92 | var unlockAudioElement = this.container; 93 | 94 | if (options.autoplay) { 95 | this.unmuteButton = document.createElement('div'); 96 | this.unmuteButton.innerHTML = VideoElement.UNMUTE_BUTTON; 97 | addStyles(this.unmuteButton, { 98 | zIndex: 2, position: 'absolute', 99 | bottom: '10px', right: '20px', 100 | width: '75px', height: '75px', 101 | margin: 'auto', 102 | opacity: '0.7', 103 | cursor: 'pointer' 104 | }); 105 | this.container.appendChild(this.unmuteButton); 106 | unlockAudioElement = this.unmuteButton; 107 | } 108 | 109 | this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement); 110 | unlockAudioElement.addEventListener('touchstart', this.unlockAudioBound, false); 111 | unlockAudioElement.addEventListener('click', this.unlockAudioBound, true); 112 | } 113 | }; 114 | 115 | VideoElement.prototype.onUnlockAudio = function (element, ev) { 116 | if (this.unmuteButton) { 117 | ev.preventDefault(); 118 | ev.stopPropagation(); 119 | } 120 | this.player.audioOut.unlock(function () { 121 | if (this.unmuteButton) { 122 | this.unmuteButton.style.display = 'none'; 123 | } 124 | element.removeEventListener('touchstart', this.unlockAudioBound); 125 | element.removeEventListener('click', this.unlockAudioBound); 126 | }.bind(this)); 127 | }; 128 | 129 | VideoElement.prototype.onClick = function (ev) { 130 | if (this.player.isPlaying) { 131 | this.player.pause(); 132 | this.playButton.style.display = 'block'; 133 | } 134 | else { 135 | this.player.play(); 136 | this.playButton.style.display = 'none'; 137 | if (this.poster) { 138 | this.poster.style.display = 'none'; 139 | } 140 | } 141 | }; 142 | 143 | VideoElement.PLAY_BUTTON = 144 | '' + 146 | '' + 148 | '' + 149 | ''; 150 | 151 | VideoElement.UNMUTE_BUTTON = 152 | '' + 153 | '' + 155 | '' + 156 | '' + 157 | '' + 158 | '' + 159 | ''; 160 | 161 | return VideoElement; 162 | 163 | })(); 164 | --------------------------------------------------------------------------------