index.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // TIP: ctrl+cmd+t,生成函数注释
  2. import { getRangeRandom } from 'billd-utils';
  3. import sparkMD5 from 'spark-md5';
  4. export const formatTimeHour = (timestamp: number) => {
  5. function addZero(num: number) {
  6. return num < 10 ? `0${num}` : num;
  7. }
  8. const date = new Date(timestamp);
  9. // 获取小时
  10. const hours = date.getHours();
  11. // 获取分钟
  12. const minutes = date.getMinutes();
  13. // 打印结果
  14. return `${addZero(hours)}:${addZero(minutes)}`;
  15. };
  16. export const formatTime = (timestamp: number) => {
  17. function addZero(num: number) {
  18. return num < 10 ? `0${num}` : num;
  19. }
  20. const date = new Date(timestamp);
  21. // 获取年份
  22. const year = date.getFullYear();
  23. // 获取月份(注意月份是从0开始的,所以要加1)
  24. const month = date.getMonth() + 1;
  25. // 获取日期
  26. const day = date.getDate();
  27. // 获取小时
  28. const hours = date.getHours();
  29. // 获取分钟
  30. const minutes = date.getMinutes();
  31. // 获取秒数
  32. const seconds = date.getSeconds();
  33. // 打印结果
  34. return `${year}-${addZero(month)}-${addZero(day)} ${addZero(hours)}:${addZero(
  35. minutes
  36. )}:${addZero(seconds)}`;
  37. };
  38. export const getLiveRoomPageUrl = (liveRoomId: number) => {
  39. return `${getHostnameUrl()}/pull/${liveRoomId}`;
  40. };
  41. export const getHostnameUrl = () => {
  42. // window.location.host,包含了域名的一个DOMString,可能在该串最后带有一个":"并跟上 URL 的端口号。
  43. // window.location.hostname,包含了域名的一个DOMString
  44. const { protocol, host } = window.location;
  45. return `${protocol}//${host}`;
  46. };
  47. /**
  48. * 根据文件内容获取hash,同一个文件不管重命名还是改文件名后缀,hash都一样
  49. * @param file
  50. * @returns
  51. */
  52. export const getHash = (file: File) => {
  53. return new Promise<{
  54. hash: string;
  55. ext: string;
  56. buffer: ArrayBuffer;
  57. }>((resolve) => {
  58. const reader = new FileReader();
  59. reader.readAsArrayBuffer(file);
  60. reader.onload = (e) => {
  61. const spark = new sparkMD5.ArrayBuffer();
  62. const buffer = e.target!.result as ArrayBuffer;
  63. spark.append(buffer);
  64. const hash = spark.end();
  65. const arr = file.name.split('.');
  66. const ext = arr[arr.length - 1];
  67. resolve({ hash, ext, buffer });
  68. };
  69. });
  70. };
  71. // 文件切片
  72. export const splitFile = (file: File) => {
  73. const chunkList: { chunk: Blob; chunkName: string }[] = [];
  74. // 先以固定的切片大小1024*100
  75. let max = 50 * 100;
  76. let count = Math.ceil(file.size / max);
  77. let index = 0;
  78. // 限定最多100个切片
  79. if (count > 100) {
  80. max = Math.ceil(file.size / 100);
  81. count = 100;
  82. }
  83. /**
  84. * 0:0,max
  85. * 1:max,2max
  86. * 2:2max,3max
  87. */
  88. while (index < count) {
  89. chunkList.push({
  90. chunkName: `${index}`,
  91. chunk: new File([file.slice(index * max, (index + 1) * max)], file.name),
  92. });
  93. index += 1;
  94. }
  95. return chunkList;
  96. };
  97. /**
  98. * 格式化倒计时
  99. * @param endTime
  100. * @param startTime
  101. */
  102. export function formatDownTime(data: {
  103. endTime: number;
  104. startTime: number;
  105. showMs?: boolean;
  106. }) {
  107. const times = (data.endTime - data.startTime) / 1000;
  108. // js获取剩余天数
  109. const d = parseInt(String(times / 60 / 60 / 24));
  110. // js获取剩余小时
  111. let h = parseInt(String((times / 60 / 60) % 24));
  112. // js获取剩余分钟
  113. let m = parseInt(String((times / 60) % 60));
  114. // js获取剩余秒
  115. let s = parseInt(String(times % 60));
  116. let ms = new Date(data.endTime).getMilliseconds();
  117. if (h < 10) {
  118. // @ts-ignore
  119. h = `0${h}`;
  120. }
  121. if (m < 10) {
  122. // @ts-ignore
  123. m = `0${m}`;
  124. }
  125. if (s < 10) {
  126. // @ts-ignore
  127. s = `0${s}`;
  128. }
  129. if (Number(ms) < 100) {
  130. if (ms < 10) {
  131. // @ts-ignore
  132. ms = `00${ms}`;
  133. } else {
  134. // @ts-ignore
  135. ms = `0${ms}`;
  136. }
  137. }
  138. const msRes = data.showMs ? `${ms}毫秒` : '';
  139. if (d > 0) {
  140. return `${d}天${h}时${m}分${s}秒${msRes}`;
  141. } else if (h > 0) {
  142. return `${h}时${m}分${s}秒${msRes}`;
  143. } else {
  144. return `${m}分${s}秒${msRes}`;
  145. }
  146. }
  147. /**
  148. * requestFileSystem保存文件,成功返回code:1,失败返回code:2
  149. * @param data
  150. */
  151. export function saveFile(data: { file: File; fileName: string }) {
  152. return new Promise<{ code: number }>((resolve) => {
  153. const { file, fileName } = data;
  154. const requestFileSystem =
  155. // @ts-ignore
  156. window.requestFileSystem || window.webkitRequestFileSystem;
  157. if (!requestFileSystem) {
  158. console.error('不支持requestFileSystem');
  159. resolve({ code: 2 });
  160. return;
  161. }
  162. function onError(err) {
  163. console.error('saveFile错误', data.fileName);
  164. console.log(err);
  165. resolve({ code: 2 });
  166. }
  167. function onFs(fs) {
  168. // 创建文件
  169. fs.root.getFile(
  170. fileName,
  171. { create: true },
  172. (fileEntry) => {
  173. // 创建文件写入流
  174. fileEntry.createWriter(function (fileWriter) {
  175. fileWriter.onwriteend = () => {
  176. // 完成后关闭文件
  177. fileWriter.abort();
  178. resolve({ code: 1 });
  179. };
  180. // 写入文件内容
  181. fileWriter.write(file);
  182. });
  183. },
  184. onError
  185. );
  186. }
  187. // Opening a file system with temporary storage
  188. requestFileSystem(
  189. // @ts-ignore
  190. window.PERSISTENT,
  191. 0,
  192. onFs,
  193. onError
  194. );
  195. });
  196. }
  197. /**
  198. * requestFileSystem读取文件,成功返回code:1,失败返回code:2
  199. * @param data
  200. */
  201. export function readFile(fileName: string) {
  202. return new Promise<{ code: number; file?: File }>((resolve) => {
  203. const requestFileSystem =
  204. // @ts-ignore
  205. window.requestFileSystem || window.webkitRequestFileSystem;
  206. if (!requestFileSystem) {
  207. console.error('不支持requestFileSystem');
  208. resolve({ code: 2 });
  209. return;
  210. }
  211. function onError(err) {
  212. console.error('readFile错误', fileName);
  213. console.log(err);
  214. resolve({ code: 2 });
  215. }
  216. function onFs(fs) {
  217. fs.root.getFile(
  218. fileName,
  219. {},
  220. (fileEntry) => {
  221. fileEntry.file(function (file) {
  222. resolve({ code: 1, file });
  223. }, onError);
  224. },
  225. onError
  226. );
  227. }
  228. // Opening a file system with temporary storage
  229. requestFileSystem(
  230. // @ts-ignore
  231. window.PERSISTENT,
  232. 0,
  233. onFs,
  234. onError
  235. );
  236. });
  237. }
  238. export function generateBase64(dom: CanvasImageSource) {
  239. const canvas = document.createElement('canvas');
  240. // @ts-ignore
  241. const { width, height } = dom.getBoundingClientRect();
  242. const rate = width / height;
  243. let ratio = 0.5;
  244. function geturl() {
  245. const coverWidth = width * ratio;
  246. const coverHeight = coverWidth / rate;
  247. canvas.width = coverWidth;
  248. canvas.height = coverHeight;
  249. canvas.getContext('2d')!.drawImage(dom, 0, 0, coverWidth, coverHeight);
  250. // webp比png的体积小非常多!因此coverWidth就可以不用压缩太夸张
  251. return canvas.toDataURL('image/webp');
  252. }
  253. let dataURL = geturl();
  254. while (dataURL.length > 1000 * 20) {
  255. ratio = ratio * 0.8;
  256. dataURL = geturl();
  257. }
  258. return dataURL;
  259. }
  260. /**
  261. * @description 获取随机字符串(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz)
  262. * @example: getRandomString(4) ===> abd3
  263. * @param {number} length
  264. * @return {*}
  265. */
  266. export const getRandomEnglishString = (length: number): string => {
  267. const str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  268. let res = '';
  269. for (let i = 0; i < length; i += 1) {
  270. res += str.charAt(getRangeRandom(0, str.length - 1));
  271. }
  272. return res;
  273. };
  274. export const createVideo = ({
  275. muted = true,
  276. autoplay = true,
  277. appendChild = false,
  278. show = false,
  279. controls = false,
  280. size = 100,
  281. }) => {
  282. const videoEl = document.createElement('video');
  283. videoEl.autoplay = autoplay;
  284. videoEl.muted = muted;
  285. videoEl.playsInline = true;
  286. videoEl.loop = true;
  287. videoEl.controls = controls;
  288. videoEl.setAttribute('webkit-playsinline', 'true');
  289. videoEl.setAttribute('x5-video-player-type', 'h5');
  290. videoEl.setAttribute('x5-video-player-fullscreen', 'true');
  291. videoEl.setAttribute('x5-video-orientation', 'portraint');
  292. videoEl.oncontextmenu = (e) => {
  293. e.preventDefault();
  294. };
  295. if (appendChild) {
  296. if (!show) {
  297. videoEl.style.width = `1px`;
  298. videoEl.style.height = `1px`;
  299. videoEl.style.opacity = '0';
  300. videoEl.style.pointerEvents = 'none';
  301. } else {
  302. videoEl.style.width = `${size}px`;
  303. videoEl.style.height = `${size}px`;
  304. }
  305. videoEl.style.position = 'fixed';
  306. videoEl.style.bottom = '0';
  307. videoEl.style.right = '0';
  308. document.body.appendChild(videoEl);
  309. }
  310. return videoEl;
  311. };
  312. export function videoToCanvas(data: {
  313. videoEl: HTMLVideoElement;
  314. resize?: (data: { w: number; h: number }) => void;
  315. }) {
  316. const { videoEl } = data;
  317. if (!videoEl) {
  318. throw new Error('videoEl不能为空!');
  319. }
  320. const canvas = document.createElement('canvas');
  321. const ctx = canvas.getContext('2d')!;
  322. let timer;
  323. let w = videoEl.videoWidth;
  324. let h = videoEl.videoHeight;
  325. function handleResize() {
  326. w = videoEl.videoWidth;
  327. h = videoEl.videoHeight;
  328. data.resize?.({ w, h });
  329. }
  330. data.resize?.({ w, h });
  331. videoEl.addEventListener('resize', handleResize);
  332. const defaultRatio = 16 / 9;
  333. function drawCanvas() {
  334. canvas.width = w;
  335. canvas.height = h;
  336. const videoRatio = w / h;
  337. // 比率的值越大,说明高的值越小
  338. // 如果视频的比率比默认dom的比率大,则说明同等宽度的情况下,视频的高度会比默认dom的高度值低
  339. if (w > h) {
  340. if (videoRatio > defaultRatio) {
  341. // 视频的比率比dom比率大
  342. canvas.style.minWidth = '100%';
  343. canvas.style.maxHeight = '100%';
  344. } else {
  345. canvas.style.minHeight = '100%';
  346. canvas.style.maxWidth = '100%';
  347. }
  348. } else {
  349. if (videoRatio > defaultRatio) {
  350. // 视频的比率比dom比率大
  351. canvas.style.minHeight = '100%';
  352. canvas.style.maxWidth = '100%';
  353. } else {
  354. canvas.style.minWidth = '100%';
  355. canvas.style.maxHeight = '100%';
  356. }
  357. }
  358. ctx.drawImage(videoEl, 0, 0, w, h);
  359. timer = requestAnimationFrame(drawCanvas);
  360. }
  361. function stopDrawing() {
  362. videoEl.removeEventListener('resize', handleResize);
  363. cancelAnimationFrame(timer);
  364. }
  365. drawCanvas();
  366. return { drawCanvas, stopDrawing, canvas };
  367. }