├── .babelrc ├── plugins ├── clappr │ ├── .babelrc │ ├── example │ │ ├── style.css │ │ └── index.html │ ├── rollup.config.js │ ├── package.json │ └── src │ │ └── streamedian.clappr.js ├── videojs │ ├── .babelrc │ ├── example │ │ ├── style.css │ │ └── index.html │ ├── rollup.config.js │ ├── package.json │ └── src │ │ └── videojs.streamedian.js └── flowplayer │ ├── .babelrc │ ├── ws_rtsp_proxy.png │ ├── example │ ├── style.css │ └── index.html │ ├── rollup.config.js │ ├── package.json │ ├── LICENSE │ └── Readme.md ├── src ├── deps │ ├── bp_event.js │ ├── bp_logger.js │ ├── jsencrypt.js │ └── bp_statemachine.js ├── core │ ├── util │ │ ├── browser.js │ │ ├── url.js │ │ ├── exp-golomb.js │ │ ├── binary.js │ │ └── md5.js │ ├── elementary │ │ ├── AACFrame.js │ │ ├── NALU.js │ │ ├── AACAsm.js │ │ └── NALUAsm.js │ ├── defs.js │ ├── base_transport.js │ ├── parsers │ │ ├── aac.js │ │ ├── ts.js │ │ ├── m3u8.js │ │ └── pes.js │ ├── base_client.js │ └── remuxer │ │ ├── base.js │ │ ├── aac.js │ │ ├── h264.js │ │ └── remuxer.js ├── client │ ├── rtsp │ │ ├── rtp │ │ │ ├── payload │ │ │ │ └── parser.js │ │ │ ├── factory.js │ │ │ └── rtp.js │ │ ├── message.js │ │ ├── session.js │ │ └── stream.js │ └── hls │ │ ├── pes_avc.js │ │ ├── pes_aac.js │ │ ├── id3.js │ │ ├── adts.js │ │ └── client.js ├── media_error.js └── recorder.js ├── frameworks └── react │ ├── public │ ├── favicon.png │ ├── manifest.json │ └── index.html │ ├── .gitignore │ ├── src │ ├── index.js │ ├── ViewInfo.jsx │ ├── index.css │ ├── ViewTunnelClient.jsx │ ├── InputSource.jsx │ ├── InputBufferDuration.jsx │ ├── Player.jsx │ ├── VideoRateControl.jsx │ ├── StreamedianPlayer.jsx │ └── serviceWorker.js │ └── package.json ├── .npmignore ├── .editorconfig ├── example ├── style.css └── test.js ├── test.js ├── rollup.videojs.config.js ├── rollup.flow.config.js ├── rollup.clappr.config.js ├── test.clappr.js ├── package.json ├── streamedian.js ├── example.js ├── .gitignore ├── player.js ├── rollup.config.js └── Server(NodeJS) /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-rollup"] 3 | } 4 | -------------------------------------------------------------------------------- /plugins/clappr/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-rollup"] 3 | } 4 | -------------------------------------------------------------------------------- /plugins/videojs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-rollup"] 3 | } 4 | -------------------------------------------------------------------------------- /plugins/flowplayer/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-rollup", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /src/deps/bp_event.js: -------------------------------------------------------------------------------- 1 | // export * from 'bp_event'; 2 | export * from '../../node_modules/bp_event/event.js'; -------------------------------------------------------------------------------- /src/deps/bp_logger.js: -------------------------------------------------------------------------------- 1 | // export * from 'bp_logger'; 2 | export * from '../../node_modules/bp_logger/logger.js'; -------------------------------------------------------------------------------- /src/deps/jsencrypt.js: -------------------------------------------------------------------------------- 1 | // export * from 'jsencrypt'; 2 | export * from '../../node_modules/jsencrypt/src/jsencrypt.js'; -------------------------------------------------------------------------------- /frameworks/react/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streamedian/html5_rtsp_player/HEAD/frameworks/react/public/favicon.png -------------------------------------------------------------------------------- /plugins/flowplayer/ws_rtsp_proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Streamedian/html5_rtsp_player/HEAD/plugins/flowplayer/ws_rtsp_proxy.png -------------------------------------------------------------------------------- /src/deps/bp_statemachine.js: -------------------------------------------------------------------------------- 1 | // export * from 'bp_statemachine'; 2 | export * from '../../node_modules/bp_statemachine/statemachine.js'; -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | examples/test.bundle.js 4 | src/client/hls 5 | src/core/parsers/pes.js 6 | src/core/parsers/ts.js 7 | webpack.config.js -------------------------------------------------------------------------------- /src/core/util/browser.js: -------------------------------------------------------------------------------- 1 | export const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 2 | 3 | export const CPU_CORES = 1;//navigator.hardwareConcurrency || 3; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/core/elementary/AACFrame.js: -------------------------------------------------------------------------------- 1 | export class AACFrame { 2 | 3 | constructor(data, dts, pts) { 4 | this.dts = dts; 5 | this.pts = pts ? pts : this.dts; 6 | 7 | this.data=data;//.subarray(offset); 8 | } 9 | 10 | getData() { 11 | return this.data; 12 | } 13 | 14 | getSize() { 15 | return this.data.byteLength; 16 | } 17 | } -------------------------------------------------------------------------------- /frameworks/react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Streamedian player", 3 | "name": "Streamedian RTSP player example", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /frameworks/react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frameworks/react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import StreamedianPlayer from './StreamedianPlayer'; 4 | import './index.css'; 5 | 6 | const App = () => ( 7 |
Enter your rtsp link to the stream, for example: "rtsp://192.168.1.1:554/h264"
27 |Change buffer duration
27 |
Have any suggestions to improve our player?
Feel free to leave comments or ideas email: streamedian.player@gmail.com
View HTML5 RTSP video player log
22 | 23 | 24 | 25 | 26 | 27 |
31 | With an empty license file, you can only watch the stream on your computer locally (intranet).
32 | If you would like to stream into the global network please take a key to activate the license.
33 | You have personal 1 month validity key in the personal cabinet.
34 | To activate key, please, use the activation application that is placed:
35 |
37 | Windows: C:\Program Files\Streamedian\WS RTSP Proxy Server\activation_app
38 | Mac OS: /Library/Application Support/Streamedian/WS RTSP Proxy Server/activation_app
39 | Linux (Ubunty, Debian, Centos, Fedora ): /usr/bin/wsp/activation_app
40 |
For more information go to documentation
42 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /frameworks/react/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read http://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/core/parsers/pes.js: -------------------------------------------------------------------------------- 1 | import {appendByteArray} from '../util/binary.js'; 2 | 3 | export class PESAsm { 4 | 5 | constructor() { 6 | this.fragments = []; 7 | this.pesLength=0; 8 | this.pesPkt = null; 9 | } 10 | 11 | parse(frag) { 12 | 13 | if (this.extPresent) { 14 | let ext = this.parseExtension(frag); 15 | ext.data = frag.subarray(ext.offset); 16 | } else { 17 | return null; 18 | } 19 | } 20 | 21 | parseHeader() { 22 | let hdr = this.fragments[0]; 23 | let pesPrefix = (hdr[0] << 16) + (hdr[1] << 8) + hdr[2]; 24 | this.extPresent = ![0xbe, 0xbf].includes(hdr[3]); 25 | if (pesPrefix === 1) { 26 | let pesLength = (hdr[4] << 8) + hdr[5]; 27 | if (pesLength) { 28 | this.pesLength = pesLength; 29 | this.hasLength = true; 30 | } else { 31 | this.hasLength = false; 32 | this.pesPkt = null; 33 | } 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | static PTSNormalize(value, reference) { 40 | 41 | let offset; 42 | if (reference === undefined) { 43 | return value; 44 | } 45 | if (reference < value) { 46 | // - 2^33 47 | offset = -8589934592; 48 | } else { 49 | // + 2^33 50 | offset = 8589934592; 51 | } 52 | /* PTS is 33bit (from 0 to 2^33 -1) 53 | if diff between value and reference is bigger than half of the amplitude (2^32) then it means that 54 | PTS looping occured. fill the gap */ 55 | while (Math.abs(value - reference) > 4294967296) { 56 | value += offset; 57 | } 58 | return value; 59 | } 60 | 61 | parseExtension(frag) { 62 | let pesFlags, pesPrefix, pesLen, pesHdrLen, pesPts, pesDts, payloadStartOffset; 63 | pesFlags = frag[1]; 64 | if (pesFlags & 0xC0) { 65 | /* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html 66 | as PTS / DTS is 33 bit we cannot use bitwise operator in JS, 67 | as Bitwise operators treat their operands as a sequence of 32 bits */ 68 | pesPts = (frag[3] & 0x0E) * 536870912 +// 1 << 29 69 | (frag[4] & 0xFF) * 4194304 +// 1 << 22 70 | (frag[5] & 0xFE) * 16384 +// 1 << 14 71 | (frag[6] & 0xFF) * 128 +// 1 << 7 72 | (frag[7] & 0xFE) / 2; 73 | // check if greater than 2^32 -1 74 | if (pesPts > 4294967295) { 75 | // decrement 2^33 76 | pesPts -= 8589934592; 77 | } 78 | if (pesFlags & 0x40) { 79 | pesDts = (frag[8] & 0x0E ) * 536870912 +// 1 << 29 80 | (frag[9] & 0xFF ) * 4194304 +// 1 << 22 81 | (frag[10] & 0xFE ) * 16384 +// 1 << 14 82 | (frag[11] & 0xFF ) * 128 +// 1 << 7 83 | (frag[12] & 0xFE ) / 2; 84 | // check if greater than 2^32 -1 85 | if (pesDts > 4294967295) { 86 | // decrement 2^33 87 | pesDts -= 8589934592; 88 | } 89 | } else { 90 | pesDts = pesPts; 91 | } 92 | 93 | pesHdrLen = frag[2]; 94 | payloadStartOffset = pesHdrLen + 9; 95 | 96 | // TODO: normalize pts/dts 97 | return {offset: payloadStartOffset, pts: pesPts, dts: pesDts}; 98 | } else { 99 | return null; 100 | } 101 | } 102 | 103 | feed(frag, shouldParse) { 104 | 105 | let res = null; 106 | if (shouldParse && this.fragments.length) { 107 | if (!this.parseHeader()) { 108 | throw new Error("Invalid PES packet"); 109 | } 110 | 111 | let offset = 6; 112 | let parsed = {}; 113 | if (this.extPresent) { 114 | // TODO: make sure fragment have necessary length 115 | parsed = this.parseExtension(this.fragments[0].subarray(6)); 116 | offset = parsed.offset; 117 | } 118 | if (!this.pesPkt) { 119 | this.pesPkt = new Uint8Array(this.pesLength); 120 | } 121 | 122 | let poffset = 0; 123 | while (this.pesLength && this.fragments.length) { 124 | let data = this.fragments.shift(); 125 | if (offset) { 126 | if (data.byteLength < offset) { 127 | offset -= data.byteLength; 128 | continue; 129 | } else { 130 | data = data.subarray(offset); 131 | this.pesLength -= offset - (this.hasLength?6:0); 132 | offset = 0; 133 | } 134 | } 135 | this.pesPkt.set(data, poffset); 136 | poffset += data.byteLength; 137 | this.pesLength -= data.byteLength; 138 | } 139 | res = {data:this.pesPkt, pts: parsed.pts, dts: parsed.dts}; 140 | } else { 141 | this.pesPkt = null; 142 | } 143 | this.pesLength += frag.byteLength; 144 | 145 | if (this.fragments.length && this.fragments[this.fragments.length-1].byteLength < 6) { 146 | this.fragments[this.fragments.length-1] = appendByteArray(this.fragments[0], frag); 147 | } else { 148 | this.fragments.push(frag); 149 | } 150 | 151 | return res; 152 | } 153 | } -------------------------------------------------------------------------------- /Server(NodeJS): -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wendy on 2017/4/13. 3 | * 4/14 可以正常播放,但是花屏。。。效果不好啊 4 | */ 5 | var wsserver = new (require('ws').Server)({port:1104}); 6 | var net = require('net'); 7 | var channelIndex = 1; 8 | wsserver.on("close", function () { 9 | console.log("close",conn); 10 | }); 11 | var channelsocket = {}; 12 | wsserver.on('connection',function(conn) { 13 | console.log("protocol",conn.protocol); 14 | var protocol = conn.protocol; 15 | if(protocol == "control") { 16 | conn.onmessage = function (msg) { 17 | console.log(msg.data); 18 | var res = wspParse(msg.data); 19 | if(res.msg == "INIT"){ 20 | var ipIndex = _ip2int(res.data.host); 21 | 22 | var channel = channelIndex++; 23 | conn.channel = channel; 24 | InitChannel(channel,ipIndex,res.data.host,res.data.port,function(){ 25 | var msg = wspMsg("200","INIT OK",res.data.seq,{"channel":channel}); 26 | conn.send(msg); 27 | },function(msgFail){ 28 | var msg = wspMsg("501",msgFail,res.data.seq); 29 | conn.send(msg); 30 | }); 31 | 32 | 33 | } 34 | else if(res.msg == "WRAP"){ 35 | //console.log(res.payload); 36 | if(channelsocket[conn.channel]) 37 | { 38 | channelsocket[conn.channel].outControlData = true; 39 | channelsocket[conn.channel].once('data',function(data){ 40 | console.log(data.toString('utf8')); 41 | var msg = wspMsg("200","WRAP OK",res.data.seq,{"channel":conn.channel},data); 42 | conn.send(msg); 43 | }); 44 | channelsocket[conn.channel].write(res.payload); 45 | } 46 | } 47 | 48 | } 49 | } 50 | else if(protocol == "data"){ 51 | //建立pipe 52 | conn.onmessage = function (msg) { 53 | console.log(msg.data); 54 | var res = wspParse(msg.data); 55 | if(res.msg == "JOIN") { 56 | channelsocket[res.data.channel].on('rtpData', function (data) { 57 | console.log(data); 58 | conn.send(data); 59 | }); 60 | 61 | var msg = wspMsg("200", "JOIN OK", res.data.seq); 62 | conn.send(msg); 63 | 64 | 65 | } 66 | } 67 | } 68 | }); 69 | function _ip2int(ip) 70 | { 71 | var num = 0; 72 | ip = ip.split("."); 73 | num = Number(ip[0]) * 256 * 256 * 256 + Number(ip[1]) * 256 * 256 + Number(ip[2]) * 256 + Number(ip[3]); 74 | num = num >>> 0; 75 | return num; 76 | } 77 | function InitChannel(channel,ipIndex,ip,prt,okFunc,failFunc){ 78 | 79 | var sock = net.connect({host:ip,port:prt},function(){ 80 | channelsocket[channel] = sock; 81 | okFunc(); 82 | sock.connectInfo = true; 83 | 84 | sock.rtpBuffer = new Buffer(2048); 85 | sock.on('data',function(data){ 86 | if(sock.outControlData) 87 | { 88 | sock.outControlData = false; 89 | return; 90 | } 91 | 92 | var flag = 0; 93 | if(sock.SubBuffer && sock.SubBufferLen>0){ 94 | flag = sock.SubBuffer.length - sock.SubBufferLen; 95 | data.copy(sock.SubBuffer,sock.SubBufferLen, 0, flag - 1); 96 | sock.emit("rtpData",sock.SubBuffer); 97 | 98 | sock.SubBufferLen = 0; 99 | } 100 | 101 | while(flag < data.length) { 102 | var len = data.readUIntBE(flag + 2, 2); 103 | sock.SubBuffer = new Buffer(4 + len); 104 | 105 | if ((flag+4+len) <= data.length) 106 | { 107 | data.copy(sock.SubBuffer, 0, flag, flag + len - 1); 108 | sock.emit("rtpData",sock.SubBuffer); 109 | sock.SubBufferLen = 0; 110 | } 111 | else { 112 | data.copy(sock.SubBuffer, 0, flag,data.length - 1); 113 | sock.SubBufferLen = data.length - flag; 114 | } 115 | flag += 4; 116 | flag += len; 117 | } 118 | }); 119 | 120 | }).on('error',function(e){ 121 | //clean all client; 122 | console.log(e); 123 | }); 124 | sock.setTimeout(1000 * 3,function() { 125 | if(!sock.connectInfo) { 126 | console.log("time out"); 127 | failFunc("relink host[" + ip + "] time out"); 128 | sock.destroy(); 129 | } 130 | }); 131 | 132 | sock.on('close',function(code){ 133 | //关闭所有子项目 134 | 135 | }); 136 | } 137 | 138 | 139 | function wspParse(data){ 140 | var payIdx = data.indexOf('\r\n\r\n'); 141 | var lines = data.substr(0, payIdx).split('\r\n'); 142 | var hdr = lines.shift().match(new RegExp('WSP/1.1\\s+(.+)')); 143 | if (hdr) { 144 | var res = { 145 | msg: hdr[1], 146 | data: {}, 147 | payload: '' 148 | }; 149 | while (lines.length) { 150 | var line = lines.shift(); 151 | if (line) { 152 | var subD = line.split(':'); 153 | res.data[subD[0]] = subD[1].trim(); 154 | } else { 155 | break; 156 | } 157 | } 158 | res.payload = data.substr(payIdx+4); 159 | return res; 160 | } 161 | return null; 162 | } 163 | function wspMsg(code,msg,seq,data,play){ 164 | 165 | var msg = "WSP/1.1 " + code + " " + msg + "\r\n"; 166 | msg += "seq:" + seq ; 167 | if(data) { 168 | for (var i in data) { 169 | msg += "\r\n"; 170 | msg += i.toString() + ":" + data[i].toString(); 171 | } 172 | } 173 | msg += "\r\n\r\n"; 174 | if(play) 175 | msg += play; 176 | 177 | return msg; 178 | } 179 | -------------------------------------------------------------------------------- /src/client/rtsp/stream.js: -------------------------------------------------------------------------------- 1 | import {getTagged} from '../../deps/bp_logger.js'; 2 | 3 | import {RTSPClientSM as RTSPClient} from './client.js'; 4 | import {Url} from '../../core/util/url.js'; 5 | import {RTSPError} from "./client"; 6 | 7 | const LOG_TAG = "rtsp:stream"; 8 | const Log = getTagged(LOG_TAG); 9 | 10 | export class RTSPStream { 11 | 12 | constructor(client, track) { 13 | this.state = null; 14 | this.client = client; 15 | this.track = track; 16 | this.rtpChannel = 1; 17 | 18 | this.stopKeepAlive(); 19 | this.keepaliveInterval = null; 20 | this.keepaliveTime = 30000; 21 | } 22 | 23 | reset() { 24 | this.stopKeepAlive(); 25 | this.client.forgetRTPChannel(this.rtpChannel); 26 | this.client = null; 27 | this.track = null; 28 | } 29 | 30 | start(lastSetupPromise = null) { 31 | if (lastSetupPromise != null) { 32 | // if a setup was already made, use the same session 33 | return lastSetupPromise.then((obj) => this.sendSetup(obj.session)) 34 | } else { 35 | return this.sendSetup(); 36 | } 37 | } 38 | 39 | stop() { 40 | return this.sendTeardown(); 41 | } 42 | 43 | getSetupURL(track) { 44 | let sessionBlock = this.client.sdp.getSessionBlock(); 45 | if (Url.isAbsolute(track.control)) { 46 | return track.control; 47 | } else if (Url.isAbsolute(`${sessionBlock.control}${track.control}`)) { 48 | return `${sessionBlock.control}${track.control}`; 49 | } else if (Url.isAbsolute(`${this.client.contentBase}${track.control}`)) { 50 | /* Check the end of the address for a separator */ 51 | if (this.client.contentBase[this.client.contentBase.length - 1] !== '/') { 52 | return `${this.client.contentBase}/${track.control}`; 53 | } 54 | 55 | /* Should probably check session level control before this */ 56 | return `${this.client.contentBase}${track.control}`; 57 | } 58 | else {//need return default 59 | return track.control; 60 | } 61 | Log.error('Can\'t determine track URL from ' + 62 | 'block.control:' + track.control + ', ' + 63 | 'session.control:' + sessionBlock.control + ', and ' + 64 | 'content-base:' + this.client.contentBase); 65 | } 66 | 67 | getControlURL() { 68 | let ctrl = this.client.sdp.getSessionBlock().control; 69 | if (Url.isAbsolute(ctrl)) { 70 | return ctrl; 71 | } else if (!ctrl || '*' === ctrl) { 72 | return this.client.contentBase; 73 | } else { 74 | return `${this.client.contentBase}${ctrl}`; 75 | } 76 | } 77 | 78 | sendKeepalive() { 79 | if (this.client.methods.includes('GET_PARAMETER')) { 80 | return this.client.sendRequest('GET_PARAMETER', this.getSetupURL(this.track), { 81 | 'Session': this.session 82 | }); 83 | } else { 84 | return this.client.sendRequest('OPTIONS', '*'); 85 | } 86 | } 87 | 88 | stopKeepAlive() { 89 | clearInterval(this.keepaliveInterval); 90 | } 91 | 92 | startKeepAlive() { 93 | this.keepaliveInterval = setInterval(() => { 94 | this.sendKeepalive().catch((e) => { 95 | Log.error(e); 96 | if (e instanceof RTSPError) { 97 | if (Number(e.data.parsed.code) == 501) { 98 | return; 99 | } 100 | } 101 | this.client.reconnect(); 102 | }); 103 | }, this.keepaliveTime); 104 | } 105 | 106 | sendRequest(_cmd, _params = {}) { 107 | let params = {}; 108 | if (this.session) { 109 | params['Session'] = this.session; 110 | } 111 | Object.assign(params, _params); 112 | return this.client.sendRequest(_cmd, this.getControlURL(), params); 113 | } 114 | 115 | sendSetup(session = null) { 116 | this.state = RTSPClient.STATE_SETUP; 117 | this.rtpChannel = this.client.interleaveChannelIndex; 118 | let interleavedChannels = this.client.interleaveChannelIndex++ + "-" + this.client.interleaveChannelIndex++; 119 | let params = { 120 | 'Transport': `RTP/AVP/TCP;unicast;interleaved=${interleavedChannels}`, 121 | 'Date': new Date().toUTCString() 122 | }; 123 | if(session){ 124 | params.Session = session; 125 | } 126 | return this.client.sendRequest('SETUP', this.getSetupURL(this.track), params).then((_data) => { 127 | this.session = _data.headers['session'].split(';'); 128 | let transport = _data.headers['transport']; 129 | if (transport) { 130 | let interleaved = transport.match(/interleaved=([0-9]+)-([0-9]+)/)[1]; 131 | if (interleaved) { 132 | this.rtpChannel = Number(interleaved); 133 | } 134 | } 135 | 136 | let sessionParamsChunks = this.session.slice(1); 137 | let sessionParams = {}; 138 | for (let chunk of sessionParamsChunks) { 139 | let kv = chunk.split('='); 140 | sessionParams[kv[0]] = kv[1]; 141 | } 142 | if (sessionParams['timeout']) { 143 | this.keepaliveInterval = Number(sessionParams['timeout']) * 500; // * 1000 / 2 144 | } 145 | /*if (!/RTP\/AVP\/TCP;unicast;interleaved=/.test(_data.headers["transport"])) { 146 | // TODO: disconnect stream and notify client 147 | throw new Error("Connection broken"); 148 | }*/ 149 | this.client.useRTPChannel(this.rtpChannel); 150 | this.startKeepAlive(); 151 | return {track: this.track, data: _data, session: this.session[0]}; 152 | }); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /plugins/videojs/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
Have any suggestions to improve our player?
Feel free to leave comments or ideas email: streamedian.player@gmail.com
View HTML5 RTSP video player log
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 136 | 137 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /plugins/flowplayer/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
Have any suggestions to improve our player?
Feel free to leave comments or ideas email: streamedian.player@gmail.com
View HTML5 RTSP video player log
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 130 | 131 | 187 | 188 | -------------------------------------------------------------------------------- /plugins/clappr/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
Have any suggestions to improve our player?
Feel free to leave comments or ideas email: streamedian.player@gmail.com
View HTML5 RTSP video player log
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 132 | 133 | 185 |