index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. import useWorker from "../lib/omnimatrix-video-player/omnimatrix-video-player.js";
  2. import FingerprintJS from "../lib/FingerprintJS/fingerprint.min.js";
  3. /**
  4. * function useWorker(url: string, className: string, device: string, callback?: Function): any
  5. @param url — wss连接地址 wss://origin/VideoShow/Common?UUID=uuid&DeviceID=deviceID&Token=token
  6. @param className — 选择器 .video-class | #video-class ...
  7. @param device — 设备ID
  8. @param callback — 画面渲染完成时的回调
  9. @returns — 包含两个Web Worker和一个关闭函数的对象
  10. */
  11. /**
  12. * @returns Promise
  13. * @description 获取指纹
  14. */
  15. function getFingerprint() {
  16. return new Promise((resolve, reject) => {
  17. // Get the visitor identifier when you need it.
  18. FingerprintJS.load()
  19. .then((fp) => fp.get())
  20. .then((result) => {
  21. // This is the visitor identifier:
  22. const visitorId = result.visitorId;
  23. resolve(visitorId);
  24. })
  25. .catch(reject);
  26. });
  27. }
  28. // 格式化时间
  29. function dateFormat(oDate, fmt) {
  30. var o = {
  31. "M+": oDate.getMonth() + 1, //月份
  32. "d+": oDate.getDate(), //日
  33. "h+": oDate.getHours(), //小时
  34. "m+": oDate.getMinutes(), //分
  35. "s+": oDate.getSeconds(), //秒
  36. "q+": Math.floor((oDate.getMonth() + 3) / 3), //季度
  37. S: oDate.getMilliseconds(), //毫秒
  38. };
  39. if (/(y+)/.test(fmt)) {
  40. fmt = fmt.replace(
  41. RegExp.$1,
  42. (oDate.getFullYear() + "").substr(4 - RegExp.$1.length)
  43. );
  44. }
  45. for (var k in o) {
  46. if (new RegExp("(" + k + ")").test(fmt)) {
  47. fmt = fmt.replace(
  48. RegExp.$1,
  49. RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
  50. );
  51. }
  52. }
  53. return fmt;
  54. }
  55. // 显示回调信息
  56. function showCBInfo(szInfo) {
  57. szInfo =
  58. "<div>" +
  59. dateFormat(new Date(), "yyyy-MM-dd hh:mm:ss") +
  60. " " +
  61. szInfo +
  62. "</div>";
  63. $("#cbinfo").html(szInfo + $("#cbinfo").html());
  64. }
  65. /**
  66. * 获取ws连接地址
  67. * @returns
  68. */
  69. function getAddress(type = "Full", params = { X: 0, Y: 0 }) {
  70. return `wss://${$("#Ip").val()}/VideoShow/Preview/${type}/Main?X=${
  71. params.X
  72. }&Y=${params.Y}`;
  73. }
  74. /**
  75. * 重置画布
  76. * @param {String} selector ——选择器
  77. */
  78. function resetVideo(selector) {
  79. let parent = $(selector).parent();
  80. $(selector).remove();
  81. parent.append(
  82. `<canvas id="${selector.split("#")[1]}" width='100%'></canvas>`
  83. );
  84. }
  85. /**
  86. *
  87. * @param {String} selector ——选择器
  88. * @returns
  89. */
  90. function getRatio(selector) {
  91. return {
  92. ratioX: $(selector).width() / $(selector).attr("width"),
  93. ratioY: $(selector).height() / $(selector).attr("height"),
  94. width: $(selector).width(),
  95. baseWidth: $(selector).attr("width"),
  96. baseHeight: $(selector).attr("height"),
  97. };
  98. }
  99. /**
  100. * 用于请求
  101. * @param {String} url ——请求地址
  102. * @param {String} method ——请求方式
  103. * @param {Object} data ——请求参数
  104. * @returns Promise
  105. */
  106. function request(url, method = "GET", data = {}) {
  107. return new Promise((resolve, reject) => {
  108. $.ajax({
  109. type: method,
  110. url,
  111. data,
  112. dataType: "json",
  113. timeout: 15000,
  114. success: function (response) {
  115. resolve(response);
  116. },
  117. error: function (error) {
  118. reject(error);
  119. },
  120. });
  121. });
  122. }
  123. // 滚轮缩放、放大逻辑
  124. function enableImageManipulation(containerId, imageId, options = {}) {
  125. const container = document.querySelector(containerId);
  126. const image = document.querySelector(imageId);
  127. let isDragging = false;
  128. let startX,
  129. startY,
  130. initialX = 0,
  131. initialY = 0,
  132. scale = 1;
  133. let currentX = 0,
  134. currentY = 0,
  135. targetX = 0,
  136. targetY = 0;
  137. // 配置选项: 最小和最大缩放比例,是否启用缩放和拖动功能
  138. const minScale = options.minScale || container.offsetWidth / image.width;
  139. const maxScale = options.maxScale || 3;
  140. const dragSpeed = options.dragSpeed || 0.2;
  141. const enableZoom = options.enableZoom !== false; // 默认启用缩放
  142. const enableDrag = options.enableDrag !== false; // 默认启用拖动
  143. // 处理缩放功能
  144. if (enableZoom) {
  145. container.addEventListener("wheel", function (event) {
  146. event.preventDefault();
  147. const { offsetX, offsetY } = event;
  148. const delta = Math.sign(event.deltaY) * -0.1;
  149. const newScale = Math.min(Math.max(scale + delta, minScale), maxScale);
  150. const dx = (offsetX - currentX) * (newScale / scale - 1);
  151. const dy = (offsetY - currentY) * (newScale / scale - 1);
  152. scale = newScale;
  153. currentX -= dx;
  154. currentY -= dy;
  155. updateImageTransform();
  156. adjustPosition();
  157. });
  158. }
  159. // 处理拖动功能
  160. if (enableDrag) {
  161. image.addEventListener("mousedown", function (event) {
  162. isDragging = true;
  163. startX = event.clientX;
  164. startY = event.clientY;
  165. const transform = image.style.transform.match(/translate\(([^)]+)\)/);
  166. [initialX, initialY] = transform
  167. ? transform[1].split(",").map(parseFloat)
  168. : [0, 0];
  169. });
  170. document.addEventListener("mousemove", function (event) {
  171. if (isDragging) {
  172. const dx = event.clientX - startX;
  173. const dy = event.clientY - startY;
  174. targetX = initialX + dx;
  175. targetY = initialY + dy;
  176. requestAnimationFrame(updateDrag);
  177. }
  178. });
  179. function reset() {
  180. isDragging = false;
  181. }
  182. document.addEventListener("mouseleave", reset);
  183. document.addEventListener("mouseup", reset);
  184. }
  185. // 更新拖动的图像位置
  186. function updateDrag() {
  187. if (!isDragging) return;
  188. currentX += (targetX - currentX) * dragSpeed;
  189. currentY += (targetY - currentY) * dragSpeed;
  190. updateImageTransform();
  191. adjustPosition();
  192. requestAnimationFrame(updateDrag);
  193. }
  194. // 更新图像的变换样式
  195. function updateImageTransform() {
  196. image.style.transform = `translate(${currentX}px, ${currentY}px) scale(${scale})`;
  197. }
  198. // 调整图像位置以限制在容器内
  199. function adjustPosition() {
  200. const rect = image.getBoundingClientRect();
  201. const containerRect = container.getBoundingClientRect();
  202. let newX = rect.left - containerRect.left;
  203. let newY = rect.top - containerRect.top;
  204. if (rect.width < containerRect.width) {
  205. newX = (containerRect.width - rect.width) / 2;
  206. } else {
  207. if (newX > 0) newX = 0;
  208. if (newX + rect.width < containerRect.width)
  209. newX = containerRect.width - rect.width;
  210. }
  211. if (rect.height < containerRect.height) {
  212. newY = (containerRect.height - rect.height) / 2;
  213. } else {
  214. if (newY > 0) newY = 0;
  215. if (newY + rect.height < containerRect.height)
  216. newY = containerRect.height - rect.height;
  217. }
  218. currentX = newX;
  219. currentY = newY;
  220. updateImageTransform();
  221. }
  222. return () => {
  223. isDragging = false;
  224. startX = 0;
  225. startY = 0;
  226. initialX = 0;
  227. initialY = 0;
  228. scale = 1;
  229. currentX = 0;
  230. currentY = 0;
  231. targetX = 0;
  232. targetY = 0;
  233. };
  234. }
  235. // 全屏方法
  236. function fullScreenDisplay(dom) {
  237. /* 全屏操作的主要方法和属性
  238. * 不同浏览器需要添加不同的前缀
  239. * chrome:webkit   firefox:moz   ie:ms   opera:o
  240. * 1.requestFullScreen():开启全屏显示
  241. * 2.cancelFullScreen():退出全屏显示:在不同的浏览器下退出全屏只能使用document来实现
  242. * 3.fullScreenElement:是否是全屏状态,也只能使用document进行判断
  243. */
  244. // 判断是否全屏,全屏则退出,非全屏则全屏
  245. if (
  246. document.fullscreenElement ||
  247. document.webkitFullscreenElement ||
  248. document.mozFullScreenElement ||
  249. document.msFullscreenElement
  250. ) {
  251. if (document.cancelFullScreen) {
  252. document.cancelFullScreen();
  253. } else if (document.webkitCancelFullScreen) {
  254. document.webkitCancelFullScreen();
  255. } else if (document.mozCancelFullScreen) {
  256. document.mozCancelFullScreen();
  257. } else if (document.msCancelFullScreen) {
  258. document.msCancelFullScreen();
  259. }
  260. } else {
  261. dom
  262. .requestFullscreen()
  263. .then(() => {
  264. // 进入全屏成功
  265. })
  266. .catch(() => {
  267. // 进入全屏失败
  268. });
  269. }
  270. }
  271. const workerObj = {}; // 视频线程对象
  272. const partObj = {}; // 获取的细节对象
  273. let partActive = 1; // 细节框选中
  274. let stateObj = {
  275. qj: false,
  276. part1: false,
  277. part2: false,
  278. }; // 全景录像状态
  279. let partXY = {}; //双击全景的x y
  280. let resetSF = null;
  281. $(async function () {
  282. // const fingerprint = await getFingerprint();
  283. $("#controlgroup").controlgroup();
  284. $("button").button();
  285. $.each($("#controlgroup > input"), function (k, v) {
  286. $(v).on("input", async () => {
  287. $("#wsUrl").text(getAddress());
  288. });
  289. });
  290. function jt(worker) {
  291. worker && worker.postMessage({ type: "jt" });
  292. }
  293. function lx(WebSocketWork, type) {
  294. stateObj[type] = !stateObj[type];
  295. if (type === "qj") {
  296. stateObj[type] ? showCBInfo(`全景开始录像`) : showCBInfo(`全景结束录像`);
  297. } else {
  298. stateObj[type] ? showCBInfo(`细节开始录像`) : showCBInfo(`细节结束录像`);
  299. }
  300. WebSocketWork &&
  301. WebSocketWork.postMessage({
  302. type: "lx",
  303. lx: stateObj[type],
  304. });
  305. }
  306. $("#wsUrl").text(getAddress());
  307. $(".play-btn-qj").click(function () {
  308. showCBInfo("全景播放");
  309. if (workerObj["qj"]) {
  310. $(".close-btn-qj").click();
  311. }
  312. let [ip = ""] = [$("#Ip").val()];
  313. if (!ip) {
  314. alert("请完善全局配置");
  315. return;
  316. }
  317. $(".video-tip").show();
  318. workerObj["qj"] = useWorker(getAddress(), "#video-demo", null, () => {
  319. showCBInfo("全景播放完成");
  320. $(".video-tip").hide();
  321. });
  322. function jtClick() {
  323. showCBInfo(`全景开始截图`);
  324. jt(workerObj["qj"].worker);
  325. }
  326. function lxClick() {
  327. lx(workerObj["qj"].WebSocketWork, "qj");
  328. $(this).toggleClass("lx-style");
  329. }
  330. // 关闭
  331. $(".close-btn-qj").click(() => {
  332. if (!workerObj["qj"]) return;
  333. $(".jt-btn-qj").off("click");
  334. $(".lx-btn-qj").off("click");
  335. workerObj["qj"].close();
  336. resetVideo("#video-demo");
  337. workerObj["qj"] = null;
  338. showCBInfo(`全景关闭`);
  339. });
  340. // 截屏
  341. $(".jt-btn-qj").click(jtClick);
  342. // 录像
  343. $(".lx-btn-qj").click(lxClick);
  344. // 双击全景
  345. $(".panorama-box #video-demo").on("dblclick", function (e) {
  346. showCBInfo("细节播放");
  347. if (!workerObj["qj"]) return;
  348. const closePart = (index) => {
  349. if (!workerObj["part" + index]) return;
  350. workerObj["part" + index].close();
  351. resetVideo(`#video-demo${index}`);
  352. workerObj["part" + index] = null;
  353. showCBInfo(`关闭细节${partActive}`);
  354. };
  355. const { ratioX, ratioY, baseWidth, baseHeight } = getRatio(
  356. ".panorama-box #video-demo"
  357. );
  358. const mouseX = e.offsetX;
  359. const mouseY = e.offsetY;
  360. console.log("双击全景", mouseX, mouseY);
  361. const params = {
  362. X: `${mouseX / ratioX > baseWidth ? baseWidth : mouseX / ratioX}`,
  363. Y: `${mouseY / ratioY > baseHeight ? baseHeight : mouseY / ratioY}`,
  364. };
  365. partXY[partActive] = params;
  366. // 关闭细节
  367. closePart(partActive);
  368. $(`.video-tip${partActive}`).show();
  369. workerObj["part" + partActive] = useWorker(
  370. getAddress("Part", params),
  371. `#video-demo${partActive}`,
  372. null,
  373. () => {
  374. showCBInfo(`细节${partActive}播放完成`);
  375. $(`.video-tip${partActive}`).hide();
  376. resetSF = enableImageManipulation(
  377. `.particulars-box .wheel${partActive}`,
  378. `#video-demo${partActive}`,
  379. {
  380. enableZoom: true, // 启用缩放
  381. enableDrag: true, // 启用拖动
  382. minScale: 1, // 最小缩放比例
  383. maxScale: 6, // 最大缩放比例
  384. dragSpeed: 0.15, // 拖动速度
  385. }
  386. );
  387. }
  388. );
  389. $.each($(".particulars-box-list>li"), function (i, v) {
  390. $(v).find("button.close-btn-part").off("click");
  391. $(v).find("button.lx-btn-part").off("click");
  392. $(v).find("button.jt-btn-part").off("click");
  393. $(v).off("mousedown");
  394. $(v).off("mouseup");
  395. let index = i + 1;
  396. // 关闭
  397. $(v)
  398. .find("button.close-btn-part")
  399. .click(function () {
  400. closePart(index);
  401. });
  402. // 截图
  403. $(v)
  404. .find("button.jt-btn-part")
  405. .click(() => {
  406. showCBInfo(`细节开始截图`);
  407. jt(workerObj["part" + index]?.worker);
  408. });
  409. // 录像
  410. $(v)
  411. .find("button.lx-btn-part")
  412. .click(() => {
  413. lx(workerObj["part" + index]?.WebSocketWork, "part" + index);
  414. workerObj["part" + index] &&
  415. $(v).find("button.lx-btn-part").toggleClass("lx-style");
  416. });
  417. });
  418. });
  419. });
  420. $.each($(".particulars-box-list>li"), function (i, v) {
  421. let index = i + 1;
  422. $(v).click(function () {
  423. if (index === partActive) return;
  424. partActive = index;
  425. $(".particulars-box-list>li").removeClass("active");
  426. $(v).addClass("active");
  427. });
  428. $(`#video-demo${index}`)
  429. .parent(".video-container")
  430. .on("dblclick", function () {
  431. resetSF();
  432. $(`#video-demo${index}`).css("transform", "translate(0,0) scale(1)");
  433. fullScreenDisplay(this);
  434. });
  435. });
  436. });