├── .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 | '';
150 |
151 | VideoElement.UNMUTE_BUTTON =
152 | '';
160 |
161 | return VideoElement;
162 |
163 | })();
164 |
--------------------------------------------------------------------------------