Player.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. const RemotePlayerMsgType = {
  2. MIN_USER_INPUT_MSG_ID: 0,
  3. MouseMove: 0,
  4. MouseDown: 1,
  5. MouseUp: 2,
  6. MouseWheel: 3,
  7. MouseDBClick: 4,
  8. WindowSize: 5,
  9. TouchDown: 6,
  10. TouchUp: 7,
  11. TouchMove: 8,
  12. Exit: 9,
  13. KeyDown: 10,
  14. KeyUp: 11,
  15. MAX_USER_INPUT_MSG_ID: 11,
  16. RELAY_JPG_REQ: 12,
  17. }
  18. // Check if Native app
  19. window.isNativeApp = (typeof(qt) == "object" && qt.webChannelTransport);
  20. if (window.isNativeApp)
  21. {
  22. if ("undefined" != typeof QWebChannel) {
  23. window.NativeAppLoad = new Promise((resolve)=>{
  24. window.gWebChannel = new QWebChannel(qt.webChannelTransport, function (channel) {
  25. window.NativeApp = {};
  26. Object.assign(window.NativeApp, channel.objects);
  27. console.info("NativeApp Loaded");
  28. resolve();
  29. });
  30. });
  31. }
  32. else {
  33. console.warn("Link qwebchannel.js to enable native call");
  34. window.isNativeApp = false;
  35. }
  36. }
  37. function RemotePlayer(config)
  38. {
  39. this.fileVersion = "For WIZPlayer 4.0.42+";
  40. let $self = this;
  41. // 配置检查
  42. config = config || {};
  43. if (!config.logger && config.debug) {
  44. const TimeToString = (t)=>{ return '['+t.toLocaleTimeString()+':'+t.getMilliseconds()+']'; };
  45. config.logger = {
  46. onRequest: function(cmdId, request, args) {
  47. let t = new Date();
  48. console.log(TimeToString(t) + ` NativeRequest:{"method": "${request}", "id":${cmdId}}`, args);
  49. },
  50. onResponse: function(cmdId, ret) {
  51. let t = new Date();
  52. console.log(TimeToString(t) + ` NativeResponse: {"id":${cmdId}}`, ret);
  53. },
  54. onConnectSignal: function(idx, signalName, callback) {
  55. console.log(TimeToString(new Date()) + ` signalConnect: {"method": "${signalName}", "id":${idx}}`, callback);
  56. },
  57. onDisconnectSignal: function(idx, signalName, callback) {
  58. console.log(TimeToString(new Date()) + ` signalDisconnect: {"method": "${signalName}", "id":${idx}}`, callback);
  59. },
  60. onSignal: function(signalName, args) {
  61. console.log(TimeToString(new Date()) + ` signalEmitted: {"method": "${signalName}"}`, args);
  62. }
  63. };
  64. }
  65. // 服务器地址
  66. config.signalServer = config.signalServer || "ws://localhost:8080";
  67. // 引擎ID
  68. config.engineId = config.engineId;
  69. // player要绑定的视频标签
  70. config.video = config.video || document.getElementsByTagName("video")[0];
  71. config.isImage = config.video.tagName == "IMG";
  72. config.video.style.transform = "rotateX(180deg)";
  73. // 是否启用自定义用户输入事件,启用则不监听视频标签上的鼠标、触屏事件,需要用户自行调用输入函数
  74. config.customInputEvent = config.customInputEvent ? true : false;
  75. // 自动调整引擎画面大小与绑定视频标签同步
  76. config.autoResize = config.autoResize ? true : false;
  77. // 限制用户每秒鼠标移动和触屏的事件个数,事件过多时操作不流畅,限制事件个数来提高操作流畅性
  78. config.limtMoveCPS = config.limtMoveCPS == undefined ? 10 : config.limtMoveCPS;
  79. // 用户操作超时时间,当用户鼠标或触屏在该时间内没有产生操作,将首次提示用户,在exitTimeout后将断开连接来节约云渲染资源
  80. config.userTimeout = isNaN(config.userTimeout) ? 1000 * 60 * 5 : config.userTimeout;
  81. // 用户操作超时后的延迟断开时间,首次提示后的将过该时间后断开连接,在真正断开前可取消resetTimer();
  82. config.exitTimeout = isNaN(config.exitTimeout) ? 1000 * 10 : config.exitTimeout;
  83. // 当前断开后引擎需要保持的时间
  84. config.maxTimeout = isNaN(config.maxTimeout) ? 0 : config.maxTimeout;
  85. $self.config = config;
  86. config.video.oncontextmenu = (e)=> { e.preventDefault() };
  87. $self.finishEventObjMap = {};
  88. $self.eventMap = {};
  89. $self.event = new Proxy({}, {
  90. set: (target, property, fn) => {
  91. this.eventMap[property] || (this.eventMap[property] = []);
  92. this.eventMap[property].push(fn);
  93. return true;
  94. }
  95. })
  96. $self.isNativeApp = false;
  97. if (window.NativeAppLoad)
  98. {
  99. $self.isNativeApp = true;
  100. $self.reconnect = ()=>{};
  101. window.NativeAppLoad.then(()=>{
  102. window.NativeApp.EngineMgr.create($self.config.engineId, { visible: false }, (objId)=>{
  103. $self.checkVideoPos = function() {
  104. var p = $self.config.video.getBoundingClientRect();
  105. var pos = [p.left, p.top];
  106. window.NativeApp.EngineMgr.update($self.config.engineId, {pos: pos});
  107. };
  108. var p = $self.config.video.getBoundingClientRect();
  109. window.NativeApp.EngineMgr.update($self.config.engineId, { pos: [p.left, p.top], visible: true });
  110. var qtTransportWrap = {
  111. send: (msg) => {
  112. qt.webChannelTransport.send(msg);
  113. }
  114. };
  115. new QWebChannel(qtTransportWrap, function (channel) {
  116. $self.Native = channel.objects[objId];
  117. $self.objects = channel.objects;
  118. window.NativeApp = {}
  119. window.webchannelDebug = $self.config.debug;
  120. Object.assign(window.NativeApp, channel.objects);
  121. if ($self.Native.ControlChannel) {
  122. $self._controlChannel = {
  123. send: (buffer) => {
  124. var str = window.btoa(String.fromCharCode(...new Uint8Array(buffer)));
  125. $self.Native.ControlChannel.send(str);
  126. }
  127. };
  128. }
  129. else {
  130. console.warn("Native not has ControlChannel");
  131. }
  132. console.info("Engine Native Loaded");
  133. $self.finishEventObjMap["NativeLoad"] = $self.Native;
  134. $self.emit("NativeLoad", $self.Native);
  135. }, $self.config.logger);
  136. });
  137. });
  138. }
  139. else {
  140. $self._ws = null;
  141. $self._pc = null;
  142. $self.resetTimer = function() {
  143. if ($self._pc && !$self.exited) {
  144. clearTimeout($self._userTimer);
  145. $self._userTimer = setTimeout(()=> {
  146. $self._userTimer = setTimeout(()=> {
  147. if (!$self.exited) {
  148. $self.exit();
  149. console.log(`长时间没有操作,已断开连接`);
  150. if (!$self.emit("TimeOutExit", $self))
  151. {
  152. alert(`长时间没有操作,已断开连接`);
  153. }
  154. }
  155. }, $self.config.exitTimeout);
  156. $self.emit("UserTimeOut", $self);
  157. }, $self.config.userTimeout);
  158. }
  159. }
  160. function configPeerConnection()
  161. {
  162. $self._controlChannel = null;
  163. var connectionCreator = RTCPeerConnection ? RTCPeerConnection : webkitRTCPeerConnection;
  164. $self._pc = new connectionCreator({ iceServers: $self.iceServers });
  165. $self._pc.onicecandidate = function(evt) {
  166. if (evt.candidate !== null) {
  167. $self._ws.send(JSON.stringify({
  168. type: "IceCandidate",
  169. data: evt.candidate
  170. }));
  171. }
  172. };
  173. $self._pc.ondatachannel = (evt) => {
  174. console.log("Get DataChannel", evt);
  175. if (evt.channel && evt.channel.label == "Control")
  176. {
  177. let checksend = ()=>
  178. {
  179. while($self._controlChannel.sendQueue.length > 0 && evt.channel.bufferedAmount <= evt.channel.bufferedAmountLowThreshold)
  180. {
  181. evt.channel.send($self._controlChannel.sendQueue.shift());
  182. }
  183. }
  184. $self._controlChannel = {
  185. sendQueue: [],
  186. send: (msg) => {
  187. if (msg.length > evt.channel.bufferedAmountLowThreshold)
  188. {
  189. let pktCount = Math.ceil(msg.length / evt.channel.bufferedAmountLowThreshold);
  190. $self._controlChannel.sendQueue.push(`NEXT_COUNT:${pktCount}`);
  191. let s = 0;
  192. while(s < msg.length) {
  193. let e = s + Math.min(evt.channel.bufferedAmountLowThreshold, msg.length - evt.channel.bufferedAmountLowThreshold);
  194. $self._controlChannel.sendQueue.push(msg.slice(s, e));
  195. s = e;
  196. }
  197. }
  198. else
  199. {
  200. $self._controlChannel.sendQueue.push(msg);
  201. }
  202. checksend();
  203. }
  204. };
  205. evt.channel.bufferedAmountLowThreshold = 65536;
  206. evt.channel.onbufferedamountlow = checksend;
  207. evt.channel.onopen = () => {
  208. new QWebChannel($self._controlChannel, function (channel) {
  209. $self.Native = channel.objects.Native;
  210. $self.objects = channel.objects;
  211. console.info("Engine Native Loaded");
  212. window.webchannelDebug = $self.config.debug;
  213. $self.finishEventObjMap["NativeLoad"] = $self.Native;
  214. $self.emit("NativeLoad", $self.Native);
  215. }, $self.config.logger);
  216. }
  217. evt.channel.onclose = () => {
  218. console.log("Datachannel close");
  219. $self._controlChannel = null;
  220. $self.Native = null;
  221. $self.nativeObjects = [];
  222. $self.finishEventObjMap["NativeLoad"] = null;
  223. $self.emit("Disconnected", "webrtc channel closed");
  224. }
  225. $self.config.video.buffers = [];
  226. $self.config.video.strBuffer = "";
  227. $self.config.video.strCount = 0;
  228. let relay_jpg_req = new ArrayBuffer(4); // for request next frame;
  229. let dataView = new DataView(relay_jpg_req);
  230. dataView.setInt32(0, RemotePlayerMsgType.RELAY_JPG_REQ, true);
  231. let lastImage = new Image();
  232. evt.channel.onmessage = (event) => {
  233. const max_data_channel_buffersize = 256 * 1024;
  234. if (typeof(event.data) == "string") {
  235. if ("function" == typeof($self._controlChannel.onmessage)) { // setup by QWebChannel
  236. $self.config.video.strCount--;
  237. if ($self.config.video.strCount == -1)
  238. {
  239. if (event.data.startsWith("NEXT_COUNT:"))
  240. {
  241. $self.config.video.strCount = parseInt(event.data.substr(11));
  242. }
  243. else
  244. {
  245. $self.config.video.strCount = 0;
  246. $self._controlChannel.onmessage({ data: JSON.parse(event.data) });
  247. }
  248. }
  249. else
  250. {
  251. $self.config.video.strBuffer += event.data;
  252. if ($self.config.video.strCount == 0)
  253. {
  254. var allStr = $self.config.video.strBuffer;
  255. $self.config.video.strBuffer = "";
  256. $self._controlChannel.onmessage({ data: JSON.parse(allStr) });
  257. }
  258. }
  259. }
  260. }
  261. else {
  262. if (event.data.byteLength > 4)
  263. {
  264. $self.config.video.buffers.push(event.data);
  265. }
  266. else
  267. {
  268. // not content package
  269. }
  270. if (event.data.byteLength < max_data_channel_buffersize)
  271. {
  272. if ($self.config.video.buffers.length > 0)
  273. {
  274. var src = URL.createObjectURL(new Blob($self.config.video.buffers));
  275. $self.config.video.buffers = [];
  276. lastImage.src = src;
  277. lastImage.decode().then(()=>{
  278. var oldSrc;
  279. if ($self.config.video.tagName == 'VIDEO')
  280. {
  281. let stream = player.config.video.srcObject;
  282. oldSrc = player.config.video.poster;
  283. player.config.video.poster = src;
  284. player.config.video.src='';
  285. player.config.video.srcObject=stream;
  286. }
  287. else // img
  288. {
  289. if ($self._controlChannel) $self._controlChannel.send(relay_jpg_req);
  290. oldSrc = $self.config.video.src;
  291. $self.config.video.src = src;
  292. }
  293. if (oldSrc) URL.revokeObjectURL(oldSrc);
  294. })
  295. .catch((err)=>{
  296. if ($self._controlChannel && $self.config.video.tagName != 'VIDEO') $self._controlChannel.send(relay_jpg_req);
  297. console.error(`decode image failed: ${err}`);
  298. URL.revokeObjectURL(src);
  299. });
  300. }
  301. else
  302. {
  303. if ($self._controlChannel && $self.config.video.tagName != 'VIDEO') $self._controlChannel.send(relay_jpg_req);
  304. }
  305. }
  306. }
  307. }
  308. }
  309. }
  310. $self._pc.ontrack = (evt) => {
  311. console.log("Get Remote Track", evt);
  312. $self._pc._track = evt.track;
  313. config.video.srcObject = evt.streams[0];
  314. config.video.play();
  315. $self.finishEventObjMap["VideoReady"] = $self.config.video;
  316. $self.emit("VideoReady", $self.config.video);
  317. $self.resetTimer();
  318. }
  319. $self._pc.onconnectionstatechange = (evt) =>
  320. {
  321. switch($self._pc.connectionState) {
  322. case "connecting":{
  323. console.log("peerconnection connecting");
  324. } break;
  325. case "connected": {
  326. console.log("peerconnection connected");
  327. $self.emit("Connected");
  328. } break;
  329. case "disconnected": {
  330. console.log("peerconnection disconnected");
  331. $self._pc.close();
  332. $self.finishEventObjMap["NativeLoad"] = null;
  333. $self.emit("Disconnected");
  334. } break;
  335. case "failed": {
  336. console.log("peerconnection connect failed");
  337. $self._pc.close();
  338. $self.emit("ConnectedFailed", evt);
  339. } break;
  340. }
  341. }
  342. }
  343. $self.reconnect = function() {
  344. if ($self.exited) return;
  345. try {
  346. $self._ws = new WebSocket(`${config.signalServer}?engineId=${config.engineId}&maxTimeout=${config.maxTimeout}&type=${config.isImage?"img":"video"}&rtcIni=${config.rtcIni}&jpgQuality=${config.jpgQuality}`);
  347. $self._ws.onopen = function () { console.log("signal websocket connected"); };
  348. $self._ws.onerror = function (e) {
  349. console.log("signal websocket error", e);
  350. $self.emit('Disconnected', "signal websocket error")
  351. }
  352. $self._ws.onmessage = function (msgData) {
  353. msgData = msgData.data;
  354. console.debug(msgData);
  355. var msg = JSON.parse(msgData);
  356. // 提示信息
  357. if (msg.type == "Message") {
  358. console.warn(msg.data);
  359. if (!$self.emit("Message", msg.data))
  360. {
  361. alert(msg.data);
  362. }
  363. }
  364. // 收到引擎会话的地址
  365. else if (msg.type == "EngineWs") {
  366. console.warn(msg.data);
  367. $self.sessionWs = msg.data;
  368. console.log(`Get Engine session connection info: ${$self.sessionWs}`);
  369. $self._engineWs = new WebSocket($self.sessionWs);
  370. let transport = {
  371. send: (msg) => {
  372. if ($self.config.debug && typeof msg == "string") console.log(msg);
  373. $self._engineWs.send(msg);
  374. }
  375. };
  376. $self._engineWs.onopen = () => {
  377. console.log(`Engine session connected`);
  378. $self.emit("Connected");
  379. new QWebChannel(transport, function (channel) {
  380. $self.Native = channel.objects.Native;
  381. $self.objects = channel.objects;
  382. console.info("Engine Native Loaded");
  383. window.webchannelDebug = $self.config.debug;
  384. $self.finishEventObjMap["NativeLoad"] = $self.Native;
  385. $self._controlChannel = {
  386. send: (buffer) => {
  387. var str = window.btoa(String.fromCharCode(...new Uint8Array(buffer)));
  388. $self.Native.ControlChannel.send(str);
  389. }
  390. };
  391. $self.emit("NativeLoad", $self.Native);
  392. }, $self.config.logger);
  393. }
  394. $self._engineWs.onclose = () => {
  395. $self._controlChannel = null;
  396. $self.Native = null;
  397. $self.nativeObjects = [];
  398. $self.finishEventObjMap["NativeLoad"] = null;
  399. $self.emit("Disconnected", "Engine session closed");
  400. }
  401. $self._engineWs.onmessage = (event) => {
  402. if (typeof(event.data) == "string") {
  403. if ("function" == typeof transport.onmessage) { // setup by QWebChannel
  404. transport.onmessage({ data: JSON.parse(event.data) });
  405. }
  406. }
  407. else {
  408. var oldSrc = $self.config.video.src;
  409. $self.config.video.src = URL.createObjectURL(event.data);
  410. if (oldSrc) URL.revokeObjectURL(oldSrc);
  411. }
  412. }
  413. }
  414. // 收到连接引擎上线消息
  415. else if (msg.type == "OpenSession") {
  416. $self.iceServers = msg.data;
  417. const connectMsg = JSON.stringify({
  418. type: "ConnectRequest",
  419. data: {}
  420. });
  421. $self._ws.send(connectMsg);
  422. }
  423. // 收到连接引擎回复
  424. else if (msg.type == "OfferToClient") {
  425. configPeerConnection();
  426. var romoteSdp = new RTCSessionDescription(msg.data);
  427. $self._pc.setRemoteDescription(romoteSdp).then(() => {
  428. $self._pc.createAnswer().then((myDesc)=>{
  429. var localSdp = new RTCSessionDescription(myDesc);
  430. $self._pc.setLocalDescription(localSdp).then(() => {
  431. $self._ws.send(JSON.stringify({
  432. type: "AnswerToEngine",
  433. data: localSdp
  434. }));
  435. }, (err)=>{ console.error("Set LocalDescription failed", err); });
  436. }, (err)=>{ console.error("CreateAnswer failed", err); });
  437. }, (err)=>{ console.error("Set RemoteDescription failed", err); });
  438. }
  439. // 收到IceCandidate消息
  440. else if (msg.type === "IceCandidate") {
  441. $self._pc.addIceCandidate(new RTCIceCandidate(msg.data));
  442. }
  443. // 收到关闭消息
  444. else if (msg.type === "CloseSession") {
  445. $self.exit();
  446. }
  447. else
  448. {
  449. console.warn(`Un processed message ${msg.type}`, msg.data);
  450. alert(`${msg.type}: ${msg.data}`);
  451. }
  452. }
  453. }
  454. catch (e)
  455. {
  456. $self.emit("Disconnected", e);
  457. }
  458. };
  459. $self.startCheckSignal = function(tripTimeCb, packetsLostCb)
  460. {
  461. let maxPacketsLost = 0, lastPacketsLost = 0;
  462. let get = (sipCb, sfuCb) => {
  463. if ($self._pc)
  464. {
  465. $self._pc.getStats().then(function (report){
  466. report.forEach((value, key) => {
  467. if (value.type === 'candidate-pair') {
  468. sipCb(value.currentRoundTripTime);
  469. }
  470. if (value.type == "inbound-rtp" || value.googContentType === 'realtime') {
  471. sfuCb(value.packetsLost);
  472. }
  473. });
  474. });
  475. }
  476. else
  477. {
  478. sipCb(99.999);
  479. sfuCb(999999);
  480. }
  481. };
  482. let lastTripTime, lastLost = -1;
  483. $self.watchSignalTimer = setInterval(()=>{
  484. get((tripTime)=>{
  485. if (lastTripTime != tripTime)
  486. {
  487. lastTripTime = tripTime;
  488. tripTimeCb(tripTime);
  489. }
  490. }, (packetsLost)=>{
  491. let lost = packetsLost - lastPacketsLost;
  492. if (lost <= 0) maxPacketsLost = 0;
  493. else if (lost > maxPacketsLost) maxPacketsLost = lost;
  494. else lost = maxPacketsLost;
  495. lastPacketsLost = packetsLost;
  496. if (lost > 0)
  497. {
  498. console.log(`WebRTC has lost ${lost} packets`);
  499. }
  500. if (lost != lastLost)
  501. {
  502. lastLost = lost;
  503. packetsLostCb(lost);
  504. }
  505. });
  506. }, 1000);
  507. }
  508. $self.stopCheckSignalstartCheckSignal = function()
  509. {
  510. if ($self.watchSignalTimer) {
  511. clearInterval($self.watchSignalTimer);
  512. $self.watchSignalTimer = null;
  513. }
  514. }
  515. }
  516. // 初始化输入事件
  517. let style = getComputedStyle(config.video);
  518. let isMouseDown = false; // 用于全局监听判断鼠标是否按下
  519. let mouseDownInfo = {}; // 鼠标按下信息
  520. let keyList = []; // 键盘按键
  521. // 注册输入事件
  522. if (!config.customInputEvent) {
  523. var events = {
  524. MouseMove:(e)=>{
  525. let x = e.offsetX / parseFloat(style.width) * 2 - 1;
  526. let y = e.offsetY / parseFloat(style.height) * 2 - 1;
  527. $self.mousemove(x, y);
  528. e.preventDefault();
  529. },
  530. MouseDown:(e)=>{
  531. mouseDownInfo = e
  532. let x = e.offsetX / parseFloat(style.width) * 2 - 1;
  533. let y = e.offsetY / parseFloat(style.height) * 2 - 1;
  534. $self.mousedown(x, y, e.button);
  535. isMouseDown = true;
  536. e.preventDefault();
  537. },
  538. MouseUp:(e)=>{
  539. if (isMouseDown) {
  540. mouseDownInfo = {}
  541. let x = e.offsetX / parseFloat(style.width) * 2 - 1;
  542. let y = e.offsetY / parseFloat(style.height) * 2 - 1;
  543. $self.mouseup(x, y, e.button);
  544. isMouseDown = false;
  545. e.preventDefault();
  546. }
  547. },
  548. MouseWheel:(e)=>{
  549. let x = e.offsetX / parseFloat(style.width) * 2 - 1;
  550. let y = e.offsetY / parseFloat(style.height) * 2 - 1;
  551. $self.mosuewheel(x, y, e.wheelDeltaY);
  552. e.preventDefault();
  553. },
  554. MouseDBClick:(e)=>{
  555. let x = e.offsetX / parseFloat(style.width) * 2 - 1;
  556. let y = e.offsetY / parseFloat(style.height) * 2 - 1;
  557. $self.mousedbclick(x, y, e.button);
  558. e.preventDefault();
  559. },
  560. TouchDown:(e)=>{
  561. let w = parseFloat(style.width);
  562. let h = parseFloat(style.height);
  563. let box = $self.config.video.getBoundingClientRect();
  564. for (let i = 0; i < e.changedTouches.length; ++i) {
  565. const t = e.changedTouches[i];
  566. let x = (t.clientX - box.left) / w * 2 - 1;
  567. let y = (t.clientY - box.top) / h * 2 - 1;
  568. y = -y;
  569. $self.touchdown(x, y, t.identifier);
  570. }
  571. e.preventDefault();
  572. },
  573. TouchUp:(e)=>{
  574. let w = parseFloat(style.width);
  575. let h = parseFloat(style.height);
  576. let box = $self.config.video.getBoundingClientRect();
  577. for (let i = 0; i < e.changedTouches.length; ++i) {
  578. const t = e.changedTouches[i];
  579. let x = (t.clientX - box.left) / w * 2 - 1;
  580. let y = (t.clientY - box.top) / h * 2 - 1;
  581. y = -y;
  582. $self.touchup(x, y, t.identifier);
  583. }
  584. e.preventDefault();
  585. },
  586. TouchMove:(e)=>{
  587. let w = parseFloat(style.width);
  588. let h = parseFloat(style.height);
  589. let box = $self.config.video.getBoundingClientRect();
  590. for (let i = 0; i < e.changedTouches.length; ++i) {
  591. const t = e.changedTouches[i];
  592. let x = (t.clientX - box.left) / w * 2 - 1;
  593. let y = (t.clientY - box.top) / h * 2 - 1;
  594. y = -y;
  595. $self.touchmove(x, y, t.identifier);
  596. }
  597. e.preventDefault();
  598. },
  599. KeyDown: (e) => {
  600. const keyIndex = keyList.findIndex(item=>{
  601. return item.keyCode === e.keyCode
  602. })
  603. if (keyIndex === -1) {
  604. keyList.push(e)
  605. }
  606. $self.keydown(e.keyCode);
  607. },
  608. KeyUp: (e) => {
  609. const keyIndex = keyList.findIndex(item=>{
  610. return item.keyCode === e.keyCode
  611. })
  612. keyList.splice(keyIndex,1)
  613. $self.keyup(e.keyCode);
  614. },
  615. Blur: (e) => {
  616. if (isMouseDown) {
  617. let x = mouseDownInfo.offsetX / parseFloat(style.width) * 2 - 1;
  618. let y = mouseDownInfo.offsetY / parseFloat(style.height) * 2 - 1;
  619. $self.mouseup(x, y, mouseDownInfo.button);
  620. isMouseDown = false;
  621. mouseDownInfo = {}
  622. }
  623. for (let i = 0; i < keyList.length; i += 1) {
  624. $self.keyup(keyList[i].keyCode);
  625. }
  626. keyList = []
  627. }
  628. }
  629. if (config.limtMoveCPS > 0)
  630. {
  631. var limtFuns = (function(){
  632. let mouse_x, mouse_y, mouse_hasChanged;
  633. let touch_hasChanged, touch_moveList;
  634. touch_moveList = new Map();
  635. return {
  636. mouse_intervalFun: () => {
  637. if (mouse_hasChanged) {
  638. mouse_hasChanged = false;
  639. $self.mousemove(mouse_x, mouse_y);
  640. }
  641. },
  642. mouse_moveFun: (evt)=>{
  643. mouse_x = evt.offsetX / parseFloat(style.width) * 2 - 1;
  644. mouse_y = evt.offsetY / parseFloat(style.height) * 2 - 1;
  645. mouse_hasChanged = true;
  646. },
  647. touch_intervalFun: ()=>{
  648. if (touch_hasChanged) {
  649. touch_hasChanged = false;
  650. touch_moveList.forEach((v,k)=>{
  651. $self.touchmove(v.x, v.y, k);
  652. });
  653. touch_moveList.clear();
  654. }
  655. },
  656. touch_moveFun: (evt)=>{
  657. let w = parseFloat(style.width);
  658. let h = parseFloat(style.height);
  659. let box = $self.config.video.getBoundingClientRect();
  660. for (let i = 0; i < evt.changedTouches.length; ++i) {
  661. const t = evt.changedTouches[i];
  662. let x = (t.clientX - box.left) / w * 2 - 1;
  663. let y = (t.clientY - box.top) / h * 2 - 1;
  664. y = -y;
  665. var o = touch_moveList.get(t.identifier);
  666. if (!o || o.x != x || o.y != y)
  667. {
  668. touch_moveList.set(t.identifier, {x: x, y: y});
  669. touch_hasChanged = true;
  670. }
  671. }
  672. }
  673. };
  674. })();
  675. var limtFunsMouseMoveInterval = setInterval(limtFuns.mouse_intervalFun, 1000 / config.limtMoveCPS);
  676. config.video.addEventListener("mousedown", (e) => {
  677. clearInterval(limtFunsMouseMoveInterval);
  678. limtFunsMouseMoveInterval = setInterval(limtFuns.mouse_intervalFun, 1000 / (config.limtMoveCPS * 2));
  679. });
  680. config.video.addEventListener("mouseup", (e)=>{
  681. if (e.buttons == 0)
  682. {
  683. clearInterval(limtFunsMouseMoveInterval);
  684. limtFunsMouseMoveInterval = setInterval(limtFuns.mouse_intervalFun, 1000 / config.limtMoveCPS);
  685. }
  686. });
  687. config.video.addEventListener("mousemove", limtFuns.mouse_moveFun);
  688. var limtTouchMoveInterval;
  689. config.video.addEventListener("touchstart", (e)=>{
  690. limtFuns.touch_intervalFun(); // flush all mouse move event
  691. if (!limtTouchMoveInterval) limtTouchMoveInterval = setInterval(limtFuns.touch_intervalFun, 1000 / (config.limtMoveCPS * 3));
  692. });
  693. config.video.addEventListener("touchend", (e)=>{
  694. limtFuns.touch_intervalFun(); // flush all mouse move event
  695. if (e.buttons == 0) clearInterval(limtTouchMoveInterval);
  696. });
  697. config.video.addEventListener("touchmove", limtFuns.touch_moveFun, { passive: false });
  698. }
  699. else
  700. {
  701. config.video.addEventListener("mousemove", events.MouseMove);
  702. config.video.addEventListener("touchmove", events.TouchMove, { passive: false });
  703. }
  704. config.video.addEventListener("mousedown", events.MouseDown);
  705. config.video.addEventListener("mouseup", events.MouseUp);
  706. var win = window;
  707. while (win.parent && win.parent != win) win = win.parent;
  708. win.addEventListener("mouseup", events.MouseUp);
  709. config.video.addEventListener("mousewheel", events.MouseWheel);
  710. config.video.addEventListener("dblclick", events.MouseDBClick);
  711. config.video.addEventListener("touchstart", events.TouchDown);
  712. config.video.addEventListener("touchend", events.TouchUp);
  713. win.addEventListener("keydown", events.KeyDown);
  714. win.addEventListener("keyup", events.KeyUp);
  715. win.addEventListener("blur", events.Blur);
  716. if (!$self.isNativeApp)
  717. {
  718. config.video.addEventListener("mousedown", $self.resetTimer);
  719. config.video.addEventListener("mousemove", $self.resetTimer);
  720. config.video.addEventListener("mouseup", $self.resetTimer);
  721. config.video.addEventListener("mousewheel", $self.resetTimer);
  722. config.video.addEventListener("touchstart", $self.resetTimer);
  723. config.video.addEventListener("touchmove", $self.resetTimer);
  724. config.video.addEventListener("touchend", $self.resetTimer);
  725. }
  726. }
  727. if (config.autoResize) {
  728. setInterval((function () {
  729. let videoWidth, videoHeight;
  730. return function (force) {
  731. let width = Math.round(parseFloat(style.width));
  732. let height = Math.round(parseFloat(style.height));
  733. if (force || videoWidth != width || videoHeight != height) {
  734. if ($self.resize(width, height)) {
  735. videoWidth = width;
  736. videoHeight = height;
  737. }
  738. }
  739. }
  740. }()), 100);
  741. }
  742. $self.reconnect();
  743. }
  744. RemotePlayer.prototype.constructor = RemotePlayer;
  745. // for event system
  746. RemotePlayer.prototype.on = function(name, fn) {
  747. this.event[name] = fn;
  748. var arg = this.finishEventObjMap[name];
  749. if (arg) fn(arg);
  750. };
  751. RemotePlayer.prototype.off = function(name) { delete this.eventMap[name]; };
  752. RemotePlayer.prototype.emit = function(name, ...val) {
  753. if (this.eventMap[name] && this.eventMap[name].length > 0) this.eventMap[name].forEach(fn => { fn(...val); })
  754. else return false;
  755. return true;
  756. };
  757. // send mousemove event
  758. RemotePlayer.prototype.mousemove = (function()
  759. {
  760. let buffer = new ArrayBuffer(12);
  761. let dataView = new DataView(buffer);
  762. dataView.setInt32(0, RemotePlayerMsgType.MouseMove, true);
  763. return function(x, y)
  764. {
  765. if (this._controlChannel)
  766. {
  767. dataView.setFloat32(4, x, true);
  768. dataView.setFloat32(8, y, true);
  769. this._controlChannel.send(buffer);
  770. //console.debug(`MouseMove: [${x}, ${y}]`);
  771. this.emit("mousemove", x,y);
  772. return true;
  773. }
  774. return false;
  775. }
  776. }());
  777. // send mousedown event
  778. RemotePlayer.prototype.mousedown = (function()
  779. {
  780. let buffer = new ArrayBuffer(16);
  781. let dataView = new DataView(buffer);
  782. dataView.setInt32(0, RemotePlayerMsgType.MouseDown, true);
  783. return function(x, y, btn)
  784. {
  785. if (this._controlChannel)
  786. {
  787. dataView.setFloat32(4, x, true);
  788. dataView.setFloat32(8, y, true);
  789. dataView.setInt32(12, btn, true);
  790. this._controlChannel.send(buffer);
  791. console.debug(`MouseDown: [${btn}, ${x}, ${y}]`);
  792. return true;
  793. }
  794. return false;
  795. }
  796. }());
  797. // send mouseup event
  798. RemotePlayer.prototype.mouseup = (function()
  799. {
  800. let buffer = new ArrayBuffer(16);
  801. let dataView = new DataView(buffer);
  802. dataView.setInt32(0, RemotePlayerMsgType.MouseUp, true);
  803. return function(x, y, btn)
  804. {
  805. if (this._controlChannel)
  806. {
  807. dataView.setFloat32(4, x, true);
  808. dataView.setFloat32(8, y, true);
  809. dataView.setInt32(12, btn, true);
  810. this._controlChannel.send(buffer);
  811. console.debug(`MouseUp:${btn} [${x}, ${y}]`);
  812. return true;
  813. }
  814. return false;
  815. }
  816. }());
  817. // send mousewheel event
  818. RemotePlayer.prototype.mosuewheel = (function()
  819. {
  820. let buffer = new ArrayBuffer(16);
  821. let dataView = new DataView(buffer);
  822. dataView.setInt32(0, RemotePlayerMsgType.MouseWheel, true);
  823. return function(x, y, delta)
  824. {
  825. if (this._controlChannel)
  826. {
  827. dataView.setFloat32(4, x, true);
  828. dataView.setFloat32(8, y, true);
  829. dataView.setInt32(12, delta, true);
  830. this._controlChannel.send(buffer);
  831. console.debug(`MouseWheel:${delta} [${x}, ${y}]`);
  832. this.emit("mosuewheel", x,y,delta);
  833. return true;
  834. }
  835. return false;
  836. }
  837. }());
  838. // send mousedbclick event
  839. RemotePlayer.prototype.mousedbclick = (function()
  840. {
  841. let buffer = new ArrayBuffer(16);
  842. let dataView = new DataView(buffer);
  843. dataView.setInt32(0, RemotePlayerMsgType.MouseDBClick, true);
  844. return function(x, y, btn)
  845. {
  846. if (this._controlChannel)
  847. {
  848. dataView.setFloat32(4, x, true);
  849. dataView.setFloat32(8, y, true);
  850. dataView.setInt32(12, btn, true);
  851. this._controlChannel.send(buffer);
  852. console.debug(`MouseDBClick:${btn} [${x}, ${y}]`);
  853. return true;
  854. }
  855. return false;
  856. }
  857. }());
  858. // send resize event
  859. RemotePlayer.prototype.resize = (function()
  860. {
  861. let buffer = new ArrayBuffer(16);
  862. let dataView = new DataView(buffer);
  863. dataView.setInt32(0, RemotePlayerMsgType.WindowSize, true);
  864. return function(w, h)
  865. {
  866. if (this._controlChannel && w >= 64 && h >= 64)
  867. {
  868. w = Math.ceil(w / 2.0) * 2.0;
  869. h = Math.ceil(h / 2.0) * 2.0;
  870. dataView.setFloat32(4, w, true);
  871. dataView.setFloat32(8, h, true);
  872. dataView.setInt32(12, 96/*DPI*/, true);
  873. this._controlChannel.send(buffer);
  874. console.debug(`WindowSize: [${w}, ${h}]`);
  875. return true;
  876. }
  877. return false;
  878. }
  879. }());
  880. // send touchdown event
  881. RemotePlayer.prototype.touchdown = (function()
  882. {
  883. let buffer = new ArrayBuffer(16);
  884. let dataView = new DataView(buffer);
  885. dataView.setInt32(0, RemotePlayerMsgType.TouchDown, true);
  886. return function(x, y, id)
  887. {
  888. if (this._controlChannel)
  889. {
  890. dataView.setFloat32(4, x, true);
  891. dataView.setFloat32(8, y, true);
  892. dataView.setInt32(12, id, true);
  893. this._controlChannel.send(buffer);
  894. console.debug(`TouchDown:${id} [${x}, ${y}]`);
  895. return true;
  896. }
  897. return false;
  898. }
  899. })();
  900. // send touchup event
  901. RemotePlayer.prototype.touchup = (function()
  902. {
  903. let buffer = new ArrayBuffer(16);
  904. let dataView = new DataView(buffer);
  905. dataView.setInt32(0, RemotePlayerMsgType.TouchUp, true);
  906. return function(x, y, id)
  907. {
  908. if (this._controlChannel)
  909. {
  910. dataView.setFloat32(4, x, true);
  911. dataView.setFloat32(8, y, true);
  912. dataView.setInt32(12, id, true);
  913. this._controlChannel.send(buffer);
  914. console.debug(`TouchUp:${id} [${x}, ${y}]`);
  915. return true;
  916. }
  917. return false;
  918. }
  919. })();
  920. // send touchmove event
  921. RemotePlayer.prototype.touchmove = (function()
  922. {
  923. let buffer = new ArrayBuffer(16);
  924. let dataView = new DataView(buffer);
  925. dataView.setInt32(0, RemotePlayerMsgType.TouchMove, true);
  926. return function(x, y, id)
  927. {
  928. if (this._controlChannel)
  929. {
  930. dataView.setFloat32(4, x, true);
  931. dataView.setFloat32(8, y, true);
  932. dataView.setInt32(12, id, true);
  933. this._controlChannel.send(buffer);
  934. console.debug(`TouchMove:${id} [${x}, ${y}]`);
  935. return true;
  936. }
  937. return false;
  938. }
  939. })();
  940. // send keydown event
  941. RemotePlayer.prototype.keydown = (function () {
  942. let buffer = new ArrayBuffer(8);
  943. let dataView = new DataView(buffer);
  944. dataView.setInt32(0, RemotePlayerMsgType.KeyDown, true);
  945. return function (keycode) {
  946. if (this._controlChannel) {
  947. dataView.setInt32(4, keycode, true);
  948. this._controlChannel.send(buffer);
  949. console.debug(`KeyDown:${keycode}`);
  950. return true;
  951. }
  952. return false;
  953. }
  954. })();
  955. // send keyup event
  956. RemotePlayer.prototype.keyup = (function () {
  957. let buffer = new ArrayBuffer(8);
  958. let dataView = new DataView(buffer);
  959. dataView.setInt32(0, RemotePlayerMsgType.KeyUp, true);
  960. return function (keycode) {
  961. if (this._controlChannel) {
  962. dataView.setInt32(4, keycode, true);
  963. this._controlChannel.send(buffer);
  964. console.debug(`KeyUp:${keycode}`);
  965. return true;
  966. }
  967. return false;
  968. }
  969. })();
  970. // send exit event
  971. RemotePlayer.prototype.exit = (function(){
  972. let buffer = new ArrayBuffer(4);
  973. let dataView = new DataView(buffer);
  974. dataView.setInt32(0, RemotePlayerMsgType.Exit, true);
  975. return function()
  976. {
  977. clearTimeout(this._userTimer);
  978. if (this._controlChannel)
  979. {
  980. this._controlChannel.send(buffer);
  981. this._controlChannel = null;
  982. }
  983. if (this._ws)
  984. {
  985. this._ws.close();
  986. this._ws = null;
  987. }
  988. if (this._pc)
  989. {
  990. this._pc.close();
  991. this._pc = null;
  992. }
  993. if (!this.exited) {
  994. this.exited = true;
  995. console.info("Exit");
  996. }
  997. return true;
  998. }
  999. })();
  1000. RemotePlayer.prototype.snapshot = function() {
  1001. var canvas = document.createElement("canvas");
  1002. canvas.width = this.config.video.width | this.config.video.videoWidth;
  1003. canvas.height = this.config.video.height | this.config.video.videoHeight;
  1004. var ctx = canvas.getContext("2d");
  1005. ctx.translate(canvas.width / 2, canvas.height / 2);
  1006. ctx.scale(1, -1);
  1007. ctx.translate(-canvas.width / 2, -canvas.height / 2);
  1008. ctx.drawImage(this.config.video, 0, 0, canvas.width, canvas.height);
  1009. var img = document.createElement("img");
  1010. img.src = canvas.toDataURL("image/jpeg", 1);
  1011. img.save = function(){
  1012. var alink = document.createElement("a");
  1013. alink.href = img.src;
  1014. alink.download = "snapshot.jpg";
  1015. alink.click();
  1016. }
  1017. return img;
  1018. };