Player.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067
  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. return true;
  772. }
  773. return false;
  774. }
  775. }());
  776. // send mousedown event
  777. RemotePlayer.prototype.mousedown = (function()
  778. {
  779. let buffer = new ArrayBuffer(16);
  780. let dataView = new DataView(buffer);
  781. dataView.setInt32(0, RemotePlayerMsgType.MouseDown, true);
  782. return function(x, y, btn)
  783. {
  784. if (this._controlChannel)
  785. {
  786. dataView.setFloat32(4, x, true);
  787. dataView.setFloat32(8, y, true);
  788. dataView.setInt32(12, btn, true);
  789. this._controlChannel.send(buffer);
  790. console.debug(`MouseDown: [${btn}, ${x}, ${y}]`);
  791. return true;
  792. }
  793. return false;
  794. }
  795. }());
  796. // send mouseup event
  797. RemotePlayer.prototype.mouseup = (function()
  798. {
  799. let buffer = new ArrayBuffer(16);
  800. let dataView = new DataView(buffer);
  801. dataView.setInt32(0, RemotePlayerMsgType.MouseUp, true);
  802. return function(x, y, btn)
  803. {
  804. if (this._controlChannel)
  805. {
  806. dataView.setFloat32(4, x, true);
  807. dataView.setFloat32(8, y, true);
  808. dataView.setInt32(12, btn, true);
  809. this._controlChannel.send(buffer);
  810. console.debug(`MouseUp:${btn} [${x}, ${y}]`);
  811. return true;
  812. }
  813. return false;
  814. }
  815. }());
  816. // send mousewheel event
  817. RemotePlayer.prototype.mosuewheel = (function()
  818. {
  819. let buffer = new ArrayBuffer(16);
  820. let dataView = new DataView(buffer);
  821. dataView.setInt32(0, RemotePlayerMsgType.MouseWheel, true);
  822. return function(x, y, delta)
  823. {
  824. if (this._controlChannel)
  825. {
  826. dataView.setFloat32(4, x, true);
  827. dataView.setFloat32(8, y, true);
  828. dataView.setInt32(12, delta, true);
  829. this._controlChannel.send(buffer);
  830. console.debug(`MouseWheel:${delta} [${x}, ${y}]`);
  831. return true;
  832. }
  833. return false;
  834. }
  835. }());
  836. // send mousedbclick event
  837. RemotePlayer.prototype.mousedbclick = (function()
  838. {
  839. let buffer = new ArrayBuffer(16);
  840. let dataView = new DataView(buffer);
  841. dataView.setInt32(0, RemotePlayerMsgType.MouseDBClick, true);
  842. return function(x, y, btn)
  843. {
  844. if (this._controlChannel)
  845. {
  846. dataView.setFloat32(4, x, true);
  847. dataView.setFloat32(8, y, true);
  848. dataView.setInt32(12, btn, true);
  849. this._controlChannel.send(buffer);
  850. console.debug(`MouseDBClick:${btn} [${x}, ${y}]`);
  851. return true;
  852. }
  853. return false;
  854. }
  855. }());
  856. // send resize event
  857. RemotePlayer.prototype.resize = (function()
  858. {
  859. let buffer = new ArrayBuffer(16);
  860. let dataView = new DataView(buffer);
  861. dataView.setInt32(0, RemotePlayerMsgType.WindowSize, true);
  862. return function(w, h)
  863. {
  864. if (this._controlChannel && w >= 64 && h >= 64)
  865. {
  866. w = Math.ceil(w / 2.0) * 2.0;
  867. h = Math.ceil(h / 2.0) * 2.0;
  868. dataView.setFloat32(4, w, true);
  869. dataView.setFloat32(8, h, true);
  870. dataView.setInt32(12, 96/*DPI*/, true);
  871. this._controlChannel.send(buffer);
  872. console.debug(`WindowSize: [${w}, ${h}]`);
  873. return true;
  874. }
  875. return false;
  876. }
  877. }());
  878. // send touchdown event
  879. RemotePlayer.prototype.touchdown = (function()
  880. {
  881. let buffer = new ArrayBuffer(16);
  882. let dataView = new DataView(buffer);
  883. dataView.setInt32(0, RemotePlayerMsgType.TouchDown, true);
  884. return function(x, y, id)
  885. {
  886. if (this._controlChannel)
  887. {
  888. dataView.setFloat32(4, x, true);
  889. dataView.setFloat32(8, y, true);
  890. dataView.setInt32(12, id, true);
  891. this._controlChannel.send(buffer);
  892. console.debug(`TouchDown:${id} [${x}, ${y}]`);
  893. return true;
  894. }
  895. return false;
  896. }
  897. })();
  898. // send touchup event
  899. RemotePlayer.prototype.touchup = (function()
  900. {
  901. let buffer = new ArrayBuffer(16);
  902. let dataView = new DataView(buffer);
  903. dataView.setInt32(0, RemotePlayerMsgType.TouchUp, true);
  904. return function(x, y, id)
  905. {
  906. if (this._controlChannel)
  907. {
  908. dataView.setFloat32(4, x, true);
  909. dataView.setFloat32(8, y, true);
  910. dataView.setInt32(12, id, true);
  911. this._controlChannel.send(buffer);
  912. console.debug(`TouchUp:${id} [${x}, ${y}]`);
  913. return true;
  914. }
  915. return false;
  916. }
  917. })();
  918. // send touchmove event
  919. RemotePlayer.prototype.touchmove = (function()
  920. {
  921. let buffer = new ArrayBuffer(16);
  922. let dataView = new DataView(buffer);
  923. dataView.setInt32(0, RemotePlayerMsgType.TouchMove, true);
  924. return function(x, y, id)
  925. {
  926. if (this._controlChannel)
  927. {
  928. dataView.setFloat32(4, x, true);
  929. dataView.setFloat32(8, y, true);
  930. dataView.setInt32(12, id, true);
  931. this._controlChannel.send(buffer);
  932. console.debug(`TouchMove:${id} [${x}, ${y}]`);
  933. return true;
  934. }
  935. return false;
  936. }
  937. })();
  938. // send keydown event
  939. RemotePlayer.prototype.keydown = (function () {
  940. let buffer = new ArrayBuffer(8);
  941. let dataView = new DataView(buffer);
  942. dataView.setInt32(0, RemotePlayerMsgType.KeyDown, true);
  943. return function (keycode) {
  944. if (this._controlChannel) {
  945. dataView.setInt32(4, keycode, true);
  946. this._controlChannel.send(buffer);
  947. console.debug(`KeyDown:${keycode}`);
  948. return true;
  949. }
  950. return false;
  951. }
  952. })();
  953. // send keyup event
  954. RemotePlayer.prototype.keyup = (function () {
  955. let buffer = new ArrayBuffer(8);
  956. let dataView = new DataView(buffer);
  957. dataView.setInt32(0, RemotePlayerMsgType.KeyUp, true);
  958. return function (keycode) {
  959. if (this._controlChannel) {
  960. dataView.setInt32(4, keycode, true);
  961. this._controlChannel.send(buffer);
  962. console.debug(`KeyUp:${keycode}`);
  963. return true;
  964. }
  965. return false;
  966. }
  967. })();
  968. // send exit event
  969. RemotePlayer.prototype.exit = (function(){
  970. let buffer = new ArrayBuffer(4);
  971. let dataView = new DataView(buffer);
  972. dataView.setInt32(0, RemotePlayerMsgType.Exit, true);
  973. return function()
  974. {
  975. clearTimeout(this._userTimer);
  976. if (this._controlChannel)
  977. {
  978. this._controlChannel.send(buffer);
  979. this._controlChannel = null;
  980. }
  981. if (this._ws)
  982. {
  983. this._ws.close();
  984. this._ws = null;
  985. }
  986. if (this._pc)
  987. {
  988. this._pc.close();
  989. this._pc = null;
  990. }
  991. if (!this.exited) {
  992. this.exited = true;
  993. console.info("Exit");
  994. }
  995. return true;
  996. }
  997. })();
  998. RemotePlayer.prototype.snapshot = function() {
  999. var canvas = document.createElement("canvas");
  1000. canvas.width = this.config.video.width | this.config.video.videoWidth;
  1001. canvas.height = this.config.video.height | this.config.video.videoHeight;
  1002. var ctx = canvas.getContext("2d");
  1003. ctx.translate(canvas.width / 2, canvas.height / 2);
  1004. ctx.scale(1, -1);
  1005. ctx.translate(-canvas.width / 2, -canvas.height / 2);
  1006. ctx.drawImage(this.config.video, 0, 0, canvas.width, canvas.height);
  1007. var img = document.createElement("img");
  1008. img.src = canvas.toDataURL("image/jpeg", 1);
  1009. img.save = function(){
  1010. var alink = document.createElement("a");
  1011. alink.href = img.src;
  1012. alink.download = "snapshot.jpg";
  1013. alink.click();
  1014. }
  1015. return img;
  1016. };