├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── dist ├── style.css ├── vue3-canvas-video-player.js └── vue3-canvas-video-player.umd.cjs ├── index.html ├── lib ├── Vue3CanvasVideoPlayer.vue ├── components │ ├── Footer │ │ ├── Controller.vue │ │ ├── Progress.vue │ │ └── index.vue │ ├── Header.vue │ ├── Main.vue │ └── Message.vue ├── compositions │ └── player.js ├── main.js └── utils.js ├── package-lock.json ├── package.json ├── public ├── bbox.gif ├── darkMode.gif ├── fps.gif ├── messageTime.gif ├── preview.gif ├── range.gif └── type.gif ├── src ├── App.vue ├── main.js └── style.css └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 GronkOut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue3 Canvas Video Player 2 | 3 | Canvas-based video player available on Vue3. If you enter FPS, it operates in frames. You can set the range and visualize the image recognition bounding-box data. 4 | 5 | [Demo](https://stackblitz.com/edit/vue3-canvas-video-player?file=src/App.vue) 6 | 7 | > Streaming videos may have delays. Local video files are recommended. 8 | 9 | ## Usage 10 | 11 | ### Install 12 | 13 | ```bash 14 | npm install --save vue3-canvas-video-player 15 | ``` 16 | 17 | ### Component 18 | 19 | ```jsx 20 | 24 | 25 | 56 | ``` 57 | 58 | ## Props 59 | 60 | ### src 61 | 62 | Path to the video source file. 63 | 64 | ### muted 65 | 66 | Defines the start-up mute state. 67 | 68 | ### autoplay 69 | 70 | Automatically play video at startup. It only works when it is muted. 71 | 72 | ### loop 73 | 74 | Which controls whether the media element should start over when it reaches the end. 75 | 76 | ### range 77 | 78 | Use seconds to set the range of the beginning and end of the video. 79 | 80 | ![image](https://github.com/GronkOut/vue3-canvas-video-player/raw/main/public/range.gif) 81 | 82 | ### fps 83 | 84 | When you enter the FPS for the video, it operates in frames. 85 | 86 | ![image](https://github.com/GronkOut/vue3-canvas-video-player/raw/main/public/fps.gif) 87 | 88 | ### bbox 89 | 90 | Visualize the image recognition bounding-box data. You can change the line thickness, fill and line color. It only works when use FPS. 91 | 92 | ![image](https://github.com/GronkOut/vue3-canvas-video-player/raw/main/public/bbox.gif) 93 | 94 | ### type 95 | 96 | You can use 'contain' mode to prevent the controller from covering the screen, or 'overlay' mode for movie viewing. 97 | 98 | ![image](https://github.com/GronkOut/vue3-canvas-video-player/raw/main/public/type.gif) 99 | 100 | ### messageTime 101 | 102 | Sets the time when messages displayed in the center of the screen disappear. No message is displayed when `0`. 103 | 104 | ![image](https://github.com/GronkOut/vue3-canvas-video-player/raw/main/public/messageTime.gif) 105 | 106 | ### preview 107 | 108 | Displays a preview in the seek bar. 109 | 110 | ![image](https://github.com/GronkOut/vue3-canvas-video-player/raw/main/public/preview.gif) 111 | 112 | ### darkMode 113 | 114 | Use dark or light mode. 115 | 116 | ![image](https://github.com/GronkOut/vue3-canvas-video-player/raw/main/public/darkMode.gif) 117 | 118 | ## Events 119 | 120 | ### loadedmetadata 121 | 122 | The `loadedmetadata` event is fired when the metadata has been loaded. 123 | 124 | ### play 125 | 126 | The `play` event is fired when the `paused` property is changed from `true` to `false`, as a result of the `play` method, or the `autoplay` attribute. 127 | 128 | ### pause 129 | 130 | The `pause` event is sent when a request to pause an activity is handled and the activity has entered its paused state, most commonly after the media has been paused through a call to the element's `pause()` method. 131 | 132 | The event is sent once the `pause()` method returns and after the media element's `paused` property has been changed to `true`. 133 | 134 | ### timeupdate 135 | 136 | The `timeupdate` event is fired when the time indicated by the `currentTime` attribute has been updated. 137 | 138 | The event frequency is dependent on the system load, but will be thrown between about 4Hz and 66Hz (assuming the event handlers don't take longer than 250ms to run). User agents are encouraged to vary the frequency of the event based on the system load and the average cost of processing the event each time, so that the UI updates are not any more frequent than the user agent can comfortably handle while decoding the video. 139 | 140 | ### volumechange 141 | 142 | The `volumechange` event is fired when the volume has changed. 143 | 144 | ### error 145 | 146 | The `error` event is fired when the resource could not be loaded due to an error (for example, a network connectivity problem). 147 | 148 | ## Shortcuts 149 | 150 | Keys | Description 151 | :- | :- 152 | `ctrl` + `alt` + `ArrowUp` | Volume up 153 | `ctrl` + `alt` + `ArrowDown` | Volume down 154 | `ctrl` + `alt` + `ArrowLeft` | Prev second or frame 155 | `ctrl` + `alt` + `ArrowRight` | Next second or frame 156 | `ctrl` + `alt` + `g` | Go to frame (FPS required) 157 | 158 | ## License 159 | 160 | [MIT](http://opensource.org/licenses/MIT) 161 | -------------------------------------------------------------------------------- /dist/style.css: -------------------------------------------------------------------------------- 1 | .cvp-header[data-v-f43b31fa]{box-sizing:border-box;display:flex;padding:0 10px;width:100%;height:var(--header-height);justify-content:space-between;align-items:center;gap:10px;background-color:var(--color-14-100)}.cvp-information[data-v-f43b31fa]{font-size:14px;white-space:nowrap;color:var(--color-00-100);opacity:.1;transition:all .2s}.cvp-information[data-active=true][data-v-f43b31fa]{opacity:1}[data-type=overlay] .cvp-header[data-v-f43b31fa]{display:none}.cvp-main[data-v-352dd94f]{overflow:hidden;height:100%;background-color:var(--color-15-100)}.cvp-video[data-v-352dd94f]{visibility:hidden;width:0;height:0}.cvp-canvas[data-v-352dd94f]{object-fit:contain;width:100%;height:100%;vertical-align:top}.cvp-progress[data-v-c1f46be8]{position:relative;height:30px;background-color:var(--color-13-100)}.cvp-progress-drag[data-v-c1f46be8]{height:100%}.cvp-progress-drag:hover+.cvp-progress-preview[data-v-c1f46be8]{bottom:100%;opacity:1}.cvp-progress-area[data-v-c1f46be8]{position:absolute;inset:0;cursor:pointer}.cvp-progress-area[data-v-c1f46be8]:before{position:absolute;inset:10px 0;background-color:var(--color-14-100);content:""}.cvp-progress-buffer[data-v-c1f46be8]{position:absolute;inset:10px auto;height:10px;background-color:var(--color-11-100);pointer-events:none}.cvp-progress-bar[data-v-c1f46be8]{position:absolute;inset:10px auto;height:10px;background-color:var(--color-05-100);pointer-events:none}.cvp-progress-bar[data-v-c1f46be8]:before{position:absolute;inset:-2px -1px -2px auto;width:2px;background-color:var(--color-00-100);filter:drop-shadow(0 0 3px var(--color-00-100));content:""}.cvp-progress-range[data-v-c1f46be8]{position:absolute;inset:10px auto;height:10px;background-color:#409eff;mix-blend-mode:color;pointer-events:none}.cvp-progress-preview[data-v-c1f46be8]{position:absolute;bottom:50%;width:100px;margin-bottom:10px;padding:5px;border-radius:3px;background-color:var(--color-13-100);transform:translate(-50%);transition:bottom .2s,opacity .2s;pointer-events:none;opacity:0}.cvp-progress-preview-video[data-v-c1f46be8]{visibility:hidden;width:0;height:0}.cvp-progress-preview-canvas[data-v-c1f46be8]{width:100%;height:auto;vertical-align:top}.cvp-progress-preview-time[data-v-c1f46be8]{margin-top:5px;text-align:center;font-size:11px;color:var(--color-05-100)}[data-type=overlay] .cvp-progress[data-v-c1f46be8]{height:10px;background-color:transparent}[data-type=overlay] .cvp-progress-area[data-v-c1f46be8]:before{inset:0;background-color:var(--color-00-010)}[data-type=overlay] .cvp-progress-buffer[data-v-c1f46be8]{top:0;background-color:var(--color-00-020)}[data-type=overlay] .cvp-progress-bar[data-v-c1f46be8]{top:0;background-color:var(--color-00-050)}[data-type=overlay] .cvp-progress-range[data-v-c1f46be8]{top:0}.cvp-controller[data-v-f2163948]{display:flex;height:30px;gap:5px;background-color:var(--color-14-100)}.cvp-controller-button[data-v-f2163948]{overflow:hidden;display:flex;width:30px;height:30px;padding:0;justify-content:center;align-items:center;border:0;background-color:transparent;opacity:.5;transition:all .2s;cursor:pointer}.cvp-controller-button[data-v-f2163948]:hover,.cvp-controller-button[data-active=true][data-v-f2163948]{opacity:1;filter:drop-shadow(0 0 3px var(--color-00-100))}.cvp-controller-icon[data-v-f2163948]{width:100%;height:100%;stroke:var(--color-00-100)}.cvp-controller-volume[data-v-f2163948]{position:relative}.cvp-controller-volume:hover .cvp-controller-volume-drag[data-v-f2163948]{height:120px;opacity:1}.cvp-controller-volume-drag[data-v-f2163948]{position:absolute;bottom:100%;width:30px;height:0;background-color:var(--color-14-100);opacity:0;transition:all .2s}.cvp-controller-volume-area[data-v-f2163948]{position:absolute;inset:10px;background-color:var(--color-13-100);cursor:pointer}.cvp-controller-volume-bar[data-v-f2163948]{position:absolute;bottom:0;width:100%;background-color:var(--color-05-100);pointer-events:none}.cvp-controller-playback-rate[data-v-f2163948]{position:relative;display:flex;height:30px;align-items:center;justify-content:center;cursor:pointer}.cvp-controller-playback-rate:hover .cvp-controller-playback-rate-list[data-v-f2163948]{height:150px;line-height:14px;opacity:1}.cvp-controller-playback-rate-text[data-v-f2163948]{overflow:hidden;padding:0 5px;height:30px;font-size:15px;text-align:center;font-weight:100;line-height:28px;color:var(--color-00-100);opacity:.5;transition:all .2s}.cvp-controller-playback-rate-text[data-v-f2163948]:hover{opacity:1;filter:drop-shadow(0 0 3px var(--color-00-100))}.cvp-controller-playback-rate-list[data-v-f2163948]{overflow:hidden;position:absolute;bottom:100%;margin:0;padding:5px 0 0;height:0;list-style:none;background-color:var(--color-14-100);opacity:0;transition:all .2s}.cvp-controller-playback-rate-button[data-v-f2163948]{display:block;padding:5px 10px;color:var(--color-05-100);border:0;background-color:transparent;cursor:pointer;opacity:.5;transition:all .2s}.cvp-controller-playback-rate-button[data-v-f2163948]:hover{opacity:1;filter:drop-shadow(0 0 3px var(--color-05-100))}[data-type=overlay] .cvp-controller[data-v-f2163948]{background-color:transparent}[data-type=overlay] .cvp-controller-volume-drag[data-v-f2163948],[data-type=overlay] .cvp-controller-playback-rate-list[data-v-f2163948]{bottom:calc(100% + 5px);background-color:var(--color-15-030)}[data-type=overlay] .cvp-controller-playback-rate-button[data-v-f2163948]{color:var(--color-03-100)}.cvp-footer[data-v-33a4d776]{display:flex;width:100%;flex-direction:column;height:var(--footer-height)}[data-type=overlay] .cvp-footer[data-v-33a4d776]{position:absolute;inset:auto 0 0;height:50px;gap:5px;background:linear-gradient(var(--color-15-000),var(--color-15-100));transform:translateY(100px);transition:all .5s ease-out}[data-type=overlay] .cvp-footer[data-active=true][data-v-33a4d776]{transform:translateY(0)}.cvp-message[data-v-8fa62035]{position:absolute;display:flex;inset:0;justify-content:center;align-items:center;pointer-events:none}.cvp-message-text[data-v-8fa62035]{padding:5px 10px;border-radius:5px;background-color:var(--color-15-050);color:var(--color-00-100);opacity:0}.cvp-message-text[data-visible=true][data-v-8fa62035]{animation:fadeIn-8fa62035 .5s 1 forwards}.cvp-message-text[data-visible=false][data-v-8fa62035]{animation:fadeOut-8fa62035 .5s 1 forwards}@keyframes fadeIn-8fa62035{0%{opacity:0}to{opacity:1}}@keyframes fadeOut-8fa62035{0%{opacity:1}to{opacity:0}}#vue3-canvas-video-player[data-v-bd75ab9b]{--header-height: 30px;--footer-height: 60px;--color-00-100: rgba(0, 0, 0, 1);--color-00-090: rgba(0, 0, 0, .9);--color-00-080: rgba(0, 0, 0, .8);--color-00-070: rgba(0, 0, 0, .7);--color-00-060: rgba(0, 0, 0, .6);--color-00-050: rgba(0, 0, 0, .5);--color-00-040: rgba(0, 0, 0, .4);--color-00-030: rgba(0, 0, 0, .3);--color-00-020: rgba(0, 0, 0, .2);--color-00-010: rgba(0, 0, 0, .1);--color-01-100: #111;--color-02-100: #222;--color-03-100: #333;--color-04-100: #444;--color-05-100: #555;--color-06-100: #666;--color-07-100: #777;--color-08-100: #888;--color-09-100: #999;--color-10-100: #aaa;--color-11-100: #bbb;--color-12-100: #ccc;--color-13-100: #ddd;--color-14-100: #eee;--color-15-000: rgba(255, 255, 255, 0);--color-15-010: rgba(255, 255, 255, .1);--color-15-020: rgba(255, 255, 255, .2);--color-15-030: rgba(255, 255, 255, .3);--color-15-040: rgba(255, 255, 255, .4);--color-15-050: rgba(255, 255, 255, .5);--color-15-060: rgba(255, 255, 255, .6);--color-15-070: rgba(255, 255, 255, .7);--color-15-080: rgba(255, 255, 255, .8);--color-15-090: rgba(255, 255, 255, .9);--color-15-100: rgba(255, 255, 255, 1);position:relative;overflow:hidden;display:grid;grid-template-rows:var(--header-height) 1fr var(--footer-height);height:100%;user-select:none}#vue3-canvas-video-player[data-dark-mode=true][data-v-bd75ab9b]{--color-00-100: rgba(255, 255, 255, 1);--color-00-090: rgba(255, 255, 255, .9);--color-00-080: rgba(255, 255, 255, .8);--color-00-070: rgba(255, 255, 255, .7);--color-00-060: rgba(255, 255, 255, .6);--color-00-050: rgba(255, 255, 255, .5);--color-00-040: rgba(255, 255, 255, .4);--color-00-030: rgba(255, 255, 255, .3);--color-00-020: rgba(255, 255, 255, .2);--color-00-010: rgba(255, 255, 255, .1);--color-01-100: #eee;--color-02-100: #ddd;--color-03-100: #ccc;--color-04-100: #bbb;--color-05-100: #aaa;--color-06-100: #999;--color-07-100: #888;--color-08-100: #777;--color-09-100: #666;--color-10-100: #555;--color-11-100: #444;--color-12-100: #333;--color-13-100: #222;--color-14-100: #111;--color-15-000: rgba(0, 0, 0, 0);--color-15-010: rgba(0, 0, 0, .1);--color-15-020: rgba(0, 0, 0, .2);--color-15-030: rgba(0, 0, 0, .3);--color-15-040: rgba(0, 0, 0, .4);--color-15-050: rgba(0, 0, 0, .5);--color-15-060: rgba(0, 0, 0, .6);--color-15-070: rgba(0, 0, 0, .7);--color-15-080: rgba(0, 0, 0, .8);--color-15-090: rgba(0, 0, 0, .9);--color-15-100: rgba(0, 0, 0, 1)}#vue3-canvas-video-player[data-type=overlay][data-v-bd75ab9b]{display:block}.cvp-block[data-v-bd75ab9b]{position:absolute;display:flex;inset:0;justify-content:center;align-items:center;color:var(--color-07-100);background-color:var(--color-15-050)} 2 | -------------------------------------------------------------------------------- /dist/vue3-canvas-video-player.js: -------------------------------------------------------------------------------- 1 | import { reactive as Se, computed as b, onMounted as ge, onUnmounted as Ve, openBlock as u, createElementBlock as f, createElementVNode as a, unref as t, toDisplayString as C, createCommentVNode as W, pushScopeId as be, popScopeId as ye, ref as J, withModifiers as we, normalizeStyle as G, Fragment as Ce, renderList as Fe, createStaticVNode as Q, createVNode as q, watch as Be, onBeforeUnmount as De } from "vue"; 2 | const K = (l) => { 3 | const e = parseInt(l), v = Math.floor(e / 3600), s = Math.floor(e % 3600 / 60), d = e % 60; 4 | return `${v < 10 ? "0" + v : v}:${s < 10 ? "0" + s : s}:${d < 10 ? "0" + d : d}`; 5 | }, z = (l, e) => Math.round(l * e), ue = (l, e) => l / e, re = (l) => l === void 0 ? !1 : new Intl.NumberFormat().format(l), o = Se({ 6 | container: { 7 | element: null, 8 | fullScreen: !1, 9 | type: "overlay", 10 | mouseDown: !1, 11 | mouseMove: !1, 12 | mouseHold: !1, 13 | resizeObserver: null 14 | }, 15 | video: { 16 | element: null, 17 | src: "", 18 | canvas: null, 19 | width: 0, 20 | height: 0, 21 | duration: 0, 22 | currentTime: 0, 23 | muted: !1, 24 | volume: 1, 25 | loop: !1, 26 | playbackRate: 1, 27 | paused: !0, 28 | fps: 0 29 | }, 30 | progress: { 31 | seekWidth: 0, 32 | seekTotal: 0, 33 | bufferWidth: 0 34 | }, 35 | preview: { 36 | enabled: !1, 37 | element: null, 38 | left: 0, 39 | time: "00:00:00" 40 | }, 41 | range: { 42 | start: 0, 43 | end: 0, 44 | left: 0, 45 | width: 0, 46 | enabled: !1 47 | }, 48 | bbox: { 49 | data: {}, 50 | textColor: "#fff", 51 | fillColor: "rgba(0, 0, 255, 0.5)", 52 | borderSize: 1, 53 | borderColor: "rgba(255, 0, 0, 0.5)", 54 | enabled: !1 55 | }, 56 | message: { 57 | time: 1e3, 58 | visible: !1, 59 | text: "" 60 | }, 61 | block: { 62 | text: "Video file has not been loaded" 63 | } 64 | }); 65 | let se = !1; 66 | function L() { 67 | const l = b(() => o.range.start > 0 && o.range.end > 0), e = b(() => o.video.fps > 0), v = b(() => Object.keys(o.bbox.data).length > 0), s = () => { 68 | const { 69 | video: { element: n, canvas: r, width: i, height: _, duration: w, fps: S }, 70 | range: { start: D } 71 | } = o, g = r.getContext("2d"), Z = (H, ee) => { 72 | const te = "...", N = g.measureText(te).width + 10; 73 | let F = H, j = g.measureText(F).width; 74 | if (j <= ee || j <= N) 75 | return F; 76 | { 77 | let oe = F.length; 78 | for (; j >= ee - N && oe-- > 0; ) 79 | F = F.substr(0, oe), j = g.measureText(F).width; 80 | return F + te; 81 | } 82 | }; 83 | r.width = i, r.height = _, n.loop = o.video.loop, l.value && (n.currentTime = D), n.paused && (o.video.paused = !0, n.pause()); 84 | const X = () => { 85 | const { 86 | video: { loop: H, src: ee }, 87 | range: { enabled: te, start: N, end: F }, 88 | bbox: { data: j, textColor: oe, fillColor: Me, borderSize: A, borderColor: Te, enabled: $e } 89 | } = o; 90 | if (!ee) { 91 | g.clearRect(0, 0, i, _); 92 | return; 93 | } 94 | g.drawImage(n, 0, 0, i, _); 95 | let ne = o.video.currentTime = n.currentTime; 96 | if (l.value && te ? (n.currentTime >= F && (H ? n.currentTime = N : (n.currentTime = F, n.pause(), o.video.paused = !0)), o.progress.seekWidth = (ne - N) / (F - N) * 100, o.progress.bufferWidth = 100) : (n.ended && H && (ne = 0), o.progress.seekWidth = ne / w * 100, n.buffered.length > 0 && (o.progress.bufferWidth = n.buffered.end(n.buffered.length - 1) / w * 100)), e.value && v.value && $e) { 97 | const Ee = z(ne, S), ae = j[Ee]; 98 | ae != null && ae.length && ae.forEach(({ label: he, xywh: R }) => { 99 | if (R && R.length === 4) { 100 | const de = R[0], ce = R[1], ve = R[2] - R[0], _e = R[3] - R[1]; 101 | g.fillStyle = Me, g.fillRect(de, ce, ve, _e), he && (g.font = "14px Roboto", g.fillStyle = oe, g.fillText(Z(he, ve), de + 5, ce + 15)), A && (g.lineWidth = A, g.strokeStyle = Te, g.strokeRect(de - A / 2, ce - A / 2, ve + A, _e + A)); 102 | } 103 | }); 104 | } 105 | n.paused && Math.floor(o.progress.seekWidth) === 100 && (o.video.paused = !0), window.requestAnimationFrame(X); 106 | }; 107 | X(); 108 | }, d = (n) => { 109 | const { 110 | video: { element: r, duration: i, fps: _ }, 111 | range: { enabled: w, start: S, end: D } 112 | } = o; 113 | r.currentTime = l.value && w ? Math.max(Math.min(n, D), S) : Math.max(Math.min(n, i), 0); 114 | const g = l.value && w ? Math.round((r.currentTime - S) / (D - S) * 100) : Math.round(r.currentTime / i * 100); 115 | B(`Seek ${K(r.currentTime)} ${e.value ? `[${re(z(r.currentTime, _))}]` : ""} (${g}%)`); 116 | }, y = ({ offsetX: n }) => { 117 | const { 118 | container: { mouseDown: r }, 119 | video: { element: i, duration: _ }, 120 | progress: { seekTotal: w }, 121 | preview: { enabled: S, element: D }, 122 | range: { enabled: g, start: Z, end: X } 123 | } = o; 124 | if (!i) 125 | return; 126 | const H = g ? n / w * (X - Z) + Z : n / w * _; 127 | H === 1 / 0 || isNaN(H) || (r && d(H), D && S && (D.currentTime = H, o.preview.time = K(H), n < 65 ? o.preview.left = 65 : n > w - 65 ? o.preview.left = w - 65 : o.preview.left = n)); 128 | }; 129 | let V; 130 | const M = ({ detail: n }) => { 131 | if (n === 1) { 132 | const { 133 | video: { element: r, paused: i }, 134 | range: { enabled: _, start: w, end: S } 135 | } = o; 136 | V = setTimeout(() => { 137 | i ? (l.value && _ && r.currentTime === S && (r.currentTime = w), r.play()) : r.pause(), B(i ? "Play" : "Pause"), o.video.paused = !i; 138 | }, 250); 139 | } else 140 | n === 2 && (clearTimeout(V), fe()); 141 | }, T = (n) => { 142 | const { 143 | video: { element: r } 144 | } = o, i = Math.max(Math.min(n, 1), 0); 145 | r.volume = o.video.volume = i, B(`Volume ${Math.floor(i * 100)}%`); 146 | }, $ = ({ offsetY: n }) => { 147 | const { 148 | container: { mouseDown: r } 149 | } = o; 150 | r && T((100 - n) / 100); 151 | }, p = () => { 152 | const { 153 | video: { element: n, muted: r } 154 | } = o; 155 | n.muted = o.video.muted = !r, B(r ? "Unmuted" : "Muted"); 156 | }, m = () => { 157 | const { 158 | video: { element: n, fps: r }, 159 | range: { end: i } 160 | } = o; 161 | if (e.value) { 162 | const _ = z(n.currentTime, r) + 1; 163 | z(i, r) === _ ? d(i) : d(ue(_, r)); 164 | } else 165 | d(n.currentTime + 1); 166 | }, x = () => { 167 | const { 168 | video: { element: n, fps: r }, 169 | range: { start: i } 170 | } = o; 171 | if (e.value) { 172 | const _ = z(n.currentTime, r) - 1; 173 | z(i, r) === _ ? d(i) : d(ue(_, r)); 174 | } else 175 | d(n.currentTime - 1); 176 | }; 177 | let E; 178 | const P = (n) => { 179 | const { 180 | container: { mouseDown: r } 181 | } = o; 182 | r ? E = setInterval(() => { 183 | n ? m() : x(); 184 | }, 60) : clearInterval(E); 185 | }, U = (n) => { 186 | const { 187 | video: { element: r } 188 | } = o; 189 | r.playbackRate = o.video.playbackRate = n, B(`Playback Rate x${n.toFixed(1)}`); 190 | }, c = (n) => { 191 | const { 192 | video: { element: r, duration: i }, 193 | progress: { seekTotal: _ }, 194 | range: { start: w, end: S } 195 | } = o; 196 | o.range.enabled !== n && (o.range.enabled = n, B(n ? "Active range" : "Inactive range")), n ? (r.currentTime = w, o.range.left = 0, o.range.width = _, o.progress.seekWidth = 0) : (r.currentTime = 0, o.range.left = Math.floor(w / i * _), o.range.width = Math.ceil(S / i * _ - w / i * _) || 1); 197 | }, I = () => { 198 | const { 199 | video: { element: n, loop: r } 200 | } = o; 201 | n.loop = o.video.loop = !r, B(r ? "Play once" : "Repeat play"); 202 | }, h = () => { 203 | const { 204 | bbox: { enabled: n } 205 | } = o; 206 | o.bbox.enabled = !n, B(n ? "Hide bounding box" : "Show bounding box"); 207 | }; 208 | let Y; 209 | const B = (n) => { 210 | const { message: r } = o, { time: i } = r; 211 | !i || (r.text = n, r.visible = !0, Y && clearTimeout(Y), Y = setTimeout(() => { 212 | r.visible = !1; 213 | }, i)); 214 | }, fe = () => { 215 | const { 216 | container: { element: n } 217 | } = o; 218 | document.fullscreenElement ? document.exitFullscreen() : n.requestFullscreen(); 219 | }, pe = ({ altKey: n, ctrlKey: r, key: i }) => { 220 | const { 221 | video: { element: _, paused: w, volume: S, fps: D } 222 | } = o; 223 | if (n && r) { 224 | if (i === "g" && e.value) { 225 | const g = window.prompt("Go to frame number", z(_.currentTime, D)); 226 | w && _.pause(), _.currentTime = ue(g, D); 227 | } 228 | i === "ArrowUp" && T(S + 0.05), i === "ArrowDown" && T(S - 0.05), i === "ArrowLeft" && x(), i === "ArrowRight" && m(); 229 | } 230 | }, me = () => { 231 | o.container.fullScreen = Boolean(document.fullscreenElement), B(`${o.container.fullScreen ? "Full" : "Normal"} screen`); 232 | }, xe = () => { 233 | const { 234 | container: { element: n }, 235 | range: { enabled: r }, 236 | preview: { enabled: i } 237 | } = o; 238 | o.progress.seekTotal = n.offsetWidth, l.value && r && c(!0), i && (o.preview.left = 0); 239 | }; 240 | let ie; 241 | const ke = () => { 242 | ie && clearTimeout(ie), o.container.mouseMove = !0, !o.container.mouseHold && (ie = setTimeout(() => { 243 | o.container.mouseMove = !1; 244 | }, 2e3)); 245 | }; 246 | return ge(() => { 247 | se || (window.addEventListener("keydown", pe), document.addEventListener("fullscreenchange", me), setTimeout(() => { 248 | o.container.resizeObserver = new ResizeObserver(xe), o.container.resizeObserver.observe(o.container.element); 249 | }, 300), se = !0); 250 | }), Ve(() => { 251 | !se || (o.container.resizeObserver.unobserve(o.container.element), window.removeEventListener("keydown", pe), document.removeEventListener("fullscreenchange", me), se = !1); 252 | }), { 253 | data: o, 254 | initialVideo: s, 255 | setVideoSeek: y, 256 | toggleVideoPlay: M, 257 | toggleVideoMute: p, 258 | changeVideoVolume: $, 259 | changeVideoFrame: P, 260 | setVideoPlaybackRate: U, 261 | setVideoRange: c, 262 | toggleVideoLoop: I, 263 | toggleVideoBbox: h, 264 | toggleFullScreen: fe, 265 | setMessage: B, 266 | handleContainerMouseMove: ke 267 | }; 268 | } 269 | const O = (l, e) => { 270 | const v = l.__vccOpts || l; 271 | for (const [s, d] of e) 272 | v[s] = d; 273 | return v; 274 | }, le = (l) => (be("data-v-f43b31fa"), l = l(), ye(), l), He = { class: "cvp-header" }, ze = ["data-active"], Pe = /* @__PURE__ */ le(() => /* @__PURE__ */ a("span", { style: { opacity: "0.5" } }, " / ", -1)), Re = { key: 0 }, We = /* @__PURE__ */ le(() => /* @__PURE__ */ a("span", { style: { opacity: "0.5" } }, " / ", -1)), Ie = ["data-active"], Le = /* @__PURE__ */ le(() => /* @__PURE__ */ a("span", { style: { opacity: "0.5" } }, " / ", -1)), Oe = { key: 0 }, Ne = /* @__PURE__ */ le(() => /* @__PURE__ */ a("span", { style: { opacity: "0.5" } }, " / ", -1)), je = { 275 | __name: "Header", 276 | setup(l) { 277 | const { data: e } = L(), v = b(() => e.range.start > 0 && e.range.end > 0), s = b(() => e.video.fps > 0), d = b(() => K(e.video.src && e.video.currentTime || 0)), y = b(() => K(e.video.src && e.video.duration || 0)), V = b(() => K(e.video.src && e.range.end || 0)), M = b(() => re(z(e.video.src && e.video.currentTime || 0, e.video.fps))), T = b(() => re(z(e.video.src && e.video.duration || 0, e.video.fps))), $ = b(() => re(z(e.video.src && e.range.end || 0, e.video.fps))); 278 | return (p, m) => (u(), f("div", He, [ 279 | a("div", { 280 | class: "cvp-information", 281 | "data-active": !t(e).range.enabled 282 | }, [ 283 | a("span", null, C(t(d)), 1), 284 | Pe, 285 | a("span", null, C(t(y)), 1), 286 | t(s) ? (u(), f("span", Re, [ 287 | a("span", null, " [ " + C(t(M)), 1), 288 | We, 289 | a("span", null, C(t(T)) + " ]", 1) 290 | ])) : W("", !0) 291 | ], 8, ze), 292 | t(v) ? (u(), f("div", { 293 | key: 0, 294 | class: "cvp-information", 295 | "data-active": t(e).range.enabled 296 | }, [ 297 | a("span", null, C(t(d)), 1), 298 | Le, 299 | a("span", null, C(t(V)), 1), 300 | t(s) ? (u(), f("span", Oe, [ 301 | a("span", null, " [ " + C(t(M)), 1), 302 | Ne, 303 | a("span", null, C(t($)) + " ]", 1) 304 | ])) : W("", !0) 305 | ], 8, Ie)) : W("", !0) 306 | ])); 307 | } 308 | }, Ae = /* @__PURE__ */ O(je, [["__scopeId", "data-v-f43b31fa"]]); 309 | const qe = { class: "cvp-main" }, Ue = ["src", "muted", "autoplay"], Ge = { 310 | __name: "Main", 311 | props: { 312 | src: { type: String, default: !1, required: !0 }, 313 | muted: { type: Boolean, default: !1 }, 314 | autoplay: { type: Boolean, default: !1 } 315 | }, 316 | emits: [ 317 | "loadedmetadata", 318 | "play", 319 | "pause", 320 | "timeupdate", 321 | "volumechange", 322 | "error" 323 | ], 324 | setup(l, { emit: e }) { 325 | const v = l, s = J(null), d = J(null), { data: y, initialVideo: V, toggleVideoPlay: M } = L(), T = ($) => { 326 | Object.assign(y, { 327 | video: { 328 | ...y.video, 329 | element: s.value, 330 | canvas: d.value, 331 | width: s.value.videoWidth, 332 | height: s.value.videoHeight, 333 | duration: s.value.duration, 334 | paused: !(v.muted === !0 && v.autoplay === !0) 335 | } 336 | }), V(), e("loadedmetadata", $); 337 | }; 338 | return ($, p) => (u(), f("div", qe, [ 339 | a("video", { 340 | class: "cvp-video", 341 | ref_key: "video", 342 | ref: s, 343 | src: l.src, 344 | muted: l.muted, 345 | autoplay: l.autoplay, 346 | onLoadedmetadata: p[0] || (p[0] = (m) => T(m)), 347 | onPlay: p[1] || (p[1] = (m) => e("play", m)), 348 | onPause: p[2] || (p[2] = (m) => e("pause", m)), 349 | onTimeupdate: p[3] || (p[3] = (m) => e("timeupdate", m)), 350 | onVolumechange: p[4] || (p[4] = (m) => e("volumechange", m)), 351 | onError: p[5] || (p[5] = (m) => e("error", m)) 352 | }, null, 40, Ue), 353 | a("canvas", { 354 | class: "cvp-canvas", 355 | ref_key: "canvas", 356 | ref: d, 357 | onClick: p[6] || (p[6] = (...m) => t(M) && t(M)(...m)) 358 | }, null, 512) 359 | ])); 360 | } 361 | }, Ke = /* @__PURE__ */ O(Ge, [["__scopeId", "data-v-352dd94f"]]); 362 | const Je = { class: "cvp-progress" }, Qe = { class: "cvp-progress-drag" }, Ye = ["src"], Ze = { class: "cvp-progress-preview-time" }, Xe = { 363 | __name: "Progress", 364 | setup(l) { 365 | const e = J(null), v = J(null), { data: s, setVideoSeek: d } = L(), y = b(() => s.range.start > 0 && s.range.end > 0), V = b(() => `${s.video.src && s.progress.bufferWidth || 0}%`), M = b(() => `${s.video.src && s.progress.seekWidth || 0}%`), T = b(() => `${s.video.src && s.range.left || 0}px`), $ = b(() => `${s.video.src && s.range.width || 0}px`), p = () => { 366 | setTimeout(() => { 367 | const { 368 | video: { width: m, height: x }, 369 | preview: { enabled: E } 370 | } = s; 371 | if (!E) 372 | return; 373 | const P = v.value.getContext("2d"), U = m * 0.3, c = x * 0.3; 374 | v.value.width = U, v.value.height = c, Object.assign(s, { 375 | preview: { 376 | ...s.preview, 377 | element: e.value 378 | } 379 | }); 380 | const I = () => { 381 | !e.value || (P.imageSmoothingEnabled = !0, P.drawImage(e.value, 0, 0, U, c), window.requestAnimationFrame(I)); 382 | }; 383 | I(); 384 | }, 100); 385 | }; 386 | return (m, x) => (u(), f("div", Je, [ 387 | a("div", Qe, [ 388 | a("div", { 389 | class: "cvp-progress-area", 390 | onMousedown: x[0] || (x[0] = we((E) => { 391 | t(s).container.mouseDown = !0, t(d)(E); 392 | }, ["self"])), 393 | onMousemove: x[1] || (x[1] = (...E) => t(d) && t(d)(...E)), 394 | onMouseup: x[2] || (x[2] = (E) => t(s).container.mouseDown = !1), 395 | onMouseleave: x[3] || (x[3] = (E) => t(s).container.mouseDown = !1) 396 | }, [ 397 | a("div", { 398 | class: "cvp-progress-buffer", 399 | style: G({ width: t(V) }) 400 | }, null, 4), 401 | a("div", { 402 | class: "cvp-progress-bar", 403 | style: G({ width: t(M) }) 404 | }, null, 4), 405 | t(y) ? (u(), f("div", { 406 | key: 0, 407 | class: "cvp-progress-range", 408 | style: G({ left: t(T), width: t($) }) 409 | }, null, 4)) : W("", !0) 410 | ], 32) 411 | ]), 412 | t(s).preview.enabled ? (u(), f("div", { 413 | key: 0, 414 | class: "cvp-progress-preview", 415 | style: G({ left: `${t(s).preview.left}px` }) 416 | }, [ 417 | a("video", { 418 | class: "cvp-progress-preview-video", 419 | ref_key: "video", 420 | ref: e, 421 | src: t(s).video.src, 422 | onLoadeddata: p 423 | }, null, 40, Ye), 424 | a("canvas", { 425 | class: "cvp-progress-preview-canvas", 426 | ref_key: "canvas", 427 | ref: v 428 | }, null, 512), 429 | a("div", Ze, C(t(s).preview.time), 1) 430 | ], 4)) : W("", !0) 431 | ])); 432 | } 433 | }, et = /* @__PURE__ */ O(Xe, [["__scopeId", "data-v-c1f46be8"]]); 434 | const k = (l) => (be("data-v-f2163948"), l = l(), ye(), l), tt = { class: "cvp-controller" }, ot = ["title"], nt = { 435 | key: 0, 436 | class: "cvp-controller-icon", 437 | viewBox: "-2 -2 28 28", 438 | "stroke-width": "1", 439 | stroke: "#ffffff", 440 | fill: "none" 441 | }, at = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { 442 | stroke: "none", 443 | d: "M0 0h24v24H0z", 444 | fill: "none" 445 | }, null, -1)), st = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { d: "M7 4v16l13 -8z" }, null, -1)), rt = [ 446 | at, 447 | st 448 | ], lt = { 449 | key: 1, 450 | class: "cvp-controller-icon", 451 | viewBox: "0 0 24 24", 452 | "stroke-width": "1", 453 | stroke: "#ffffff", 454 | fill: "none" 455 | }, it = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { 456 | stroke: "none", 457 | d: "M0 0h24v24H0z", 458 | fill: "none" 459 | }, null, -1)), dt = /* @__PURE__ */ k(() => /* @__PURE__ */ a("rect", { 460 | x: "6", 461 | y: "5", 462 | width: "4", 463 | height: "14", 464 | rx: "1" 465 | }, null, -1)), ct = /* @__PURE__ */ k(() => /* @__PURE__ */ a("rect", { 466 | x: "14", 467 | y: "5", 468 | width: "4", 469 | height: "14", 470 | rx: "1" 471 | }, null, -1)), vt = [ 472 | it, 473 | dt, 474 | ct 475 | ], ut = { class: "cvp-controller-volume" }, ft = ["title"], pt = { 476 | key: 0, 477 | class: "cvp-controller-icon", 478 | viewBox: "-2 -2 28 28", 479 | "stroke-width": "1", 480 | stroke: "#ffffff", 481 | fill: "none" 482 | }, mt = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { 483 | stroke: "none", 484 | d: "M0 0h24v24H0z", 485 | fill: "none" 486 | }, null, -1)), ht = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { d: "M6 15h-2a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h2l3.5 -4.5a0.8 .8 0 0 1 1.5 .5v14a0.8 .8 0 0 1 -1.5 .5l-3.5 -4.5" }, null, -1)), _t = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { d: "M16 10l4 4m0 -4l-4 4" }, null, -1)), gt = [ 487 | mt, 488 | ht, 489 | _t 490 | ], bt = { 491 | key: 1, 492 | class: "cvp-controller-icon", 493 | viewBox: "-2 -2 28 28", 494 | "stroke-width": "1", 495 | stroke: "#ffffff", 496 | fill: "none" 497 | }, yt = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { 498 | stroke: "none", 499 | d: "M0 0h24v24H0z", 500 | fill: "none" 501 | }, null, -1)), wt = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { d: "M15 8a5 5 0 0 1 0 8" }, null, -1)), xt = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { d: "M17.7 5a9 9 0 0 1 0 14" }, null, -1)), kt = /* @__PURE__ */ k(() => /* @__PURE__ */ a("path", { d: "M6 15h-2a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h2l3.5 -4.5a0.8 .8 0 0 1 1.5 .5v14a0.8 .8 0 0 1 -1.5 .5l-3.5 -4.5" }, null, -1)), Mt = [ 502 | yt, 503 | wt, 504 | xt, 505 | kt 506 | ], Tt = { class: "cvp-controller-volume-drag" }, $t = /* @__PURE__ */ k(() => /* @__PURE__ */ a("svg", { 507 | class: "cvp-controller-icon", 508 | viewBox: "-1 -1 26 26", 509 | "stroke-width": "1", 510 | stroke: "#ffffff", 511 | fill: "none" 512 | }, [ 513 | /* @__PURE__ */ a("path", { 514 | stroke: "none", 515 | d: "M0 0h24v24H0z", 516 | fill: "none" 517 | }), 518 | /* @__PURE__ */ a("path", { d: "M20 5v14l-12 -7z" }), 519 | /* @__PURE__ */ a("line", { 520 | x1: "4", 521 | y1: "5", 522 | x2: "4", 523 | y2: "19" 524 | }) 525 | ], -1)), Et = [ 526 | $t 527 | ], St = /* @__PURE__ */ k(() => /* @__PURE__ */ a("svg", { 528 | class: "cvp-controller-icon", 529 | viewBox: "-1 -1 26 26", 530 | "stroke-width": "1", 531 | stroke: "#ffffff", 532 | fill: "none" 533 | }, [ 534 | /* @__PURE__ */ a("path", { 535 | stroke: "none", 536 | d: "M0 0h24v24H0z", 537 | fill: "none" 538 | }), 539 | /* @__PURE__ */ a("path", { d: "M4 5v14l12 -7z" }), 540 | /* @__PURE__ */ a("line", { 541 | x1: "20", 542 | y1: "5", 543 | x2: "20", 544 | y2: "19" 545 | }) 546 | ], -1)), Vt = [ 547 | St 548 | ], Ct = /* @__PURE__ */ k(() => /* @__PURE__ */ a("div", { style: { flex: "1" } }, null, -1)), Ft = { class: "cvp-controller-playback-rate" }, Bt = { 549 | class: "cvp-controller-playback-rate-text", 550 | title: "Playback rate" 551 | }, Dt = { class: "cvp-controller-playback-rate-list" }, Ht = { class: "cvp-controller-playback-rate-item" }, zt = ["onClick"], Pt = ["data-active", "title"], Rt = { 552 | key: 0, 553 | class: "cvp-controller-icon", 554 | viewBox: "-4 -4 32 32", 555 | "stroke-width": "1", 556 | stroke: "#ffffff", 557 | fill: "none" 558 | }, Wt = /* @__PURE__ */ Q('', 5), It = [ 559 | Wt 560 | ], Lt = { 561 | key: 1, 562 | class: "cvp-controller-icon", 563 | viewBox: "-4 -4 32 32", 564 | "stroke-width": "1", 565 | stroke: "#ffffff", 566 | fill: "none" 567 | }, Ot = /* @__PURE__ */ Q('', 5), Nt = [ 568 | Ot 569 | ], jt = ["data-active", "title"], At = /* @__PURE__ */ k(() => /* @__PURE__ */ a("svg", { 570 | class: "cvp-controller-icon", 571 | viewBox: "-4 -4 32 32", 572 | "stroke-width": "1.5", 573 | stroke: "#ffffff", 574 | fill: "none" 575 | }, [ 576 | /* @__PURE__ */ a("path", { 577 | stroke: "none", 578 | d: "M0 0h24v24H0z", 579 | fill: "none" 580 | }), 581 | /* @__PURE__ */ a("path", { d: "M4 12v-3a3 3 0 0 1 3 -3h13m-3 -3l3 3l-3 3" }), 582 | /* @__PURE__ */ a("path", { d: "M20 12v3a3 3 0 0 1 -3 3h-13m3 3l-3 -3l3 -3" }) 583 | ], -1)), qt = [ 584 | At 585 | ], Ut = ["data-active", "title"], Gt = /* @__PURE__ */ Q('', 1), Kt = [ 586 | Gt 587 | ], Jt = ["title"], Qt = { 588 | key: 0, 589 | class: "cvp-controller-icon", 590 | viewBox: "0 0 24 24", 591 | "stroke-width": "1", 592 | stroke: "#ffffff", 593 | fill: "none" 594 | }, Yt = /* @__PURE__ */ Q('', 5), Zt = [ 595 | Yt 596 | ], Xt = { 597 | key: 1, 598 | class: "cvp-controller-icon", 599 | viewBox: "-2 -2 28 28", 600 | "stroke-width": "1", 601 | stroke: "#ffffff", 602 | fill: "none" 603 | }, eo = /* @__PURE__ */ Q('', 5), to = [ 604 | eo 605 | ], oo = { 606 | __name: "Controller", 607 | setup(l) { 608 | const { 609 | data: e, 610 | toggleVideoPlay: v, 611 | toggleVideoMute: s, 612 | changeVideoVolume: d, 613 | changeVideoFrame: y, 614 | setVideoPlaybackRate: V, 615 | setVideoRange: M, 616 | toggleVideoLoop: T, 617 | toggleVideoBbox: $, 618 | toggleFullScreen: p 619 | } = L(), m = b(() => e.range.start > 0 && e.range.end > 0), x = b(() => e.video.fps > 0), E = b(() => Object.keys(e.bbox.data).length > 0), P = () => { 620 | M(!e.range.enabled); 621 | }; 622 | return (U, c) => { 623 | var I; 624 | return u(), f("div", tt, [ 625 | a("button", { 626 | class: "cvp-controller-button", 627 | title: t(e).video.paused ? "Play" : "Pause", 628 | onClick: c[0] || (c[0] = (...h) => t(v) && t(v)(...h)) 629 | }, [ 630 | t(e).video.paused ? (u(), f("svg", nt, rt)) : (u(), f("svg", lt, vt)) 631 | ], 8, ot), 632 | a("div", ut, [ 633 | a("button", { 634 | class: "cvp-controller-button", 635 | title: t(e).video.muted ? "Unmute" : "Mute", 636 | onClick: c[1] || (c[1] = (...h) => t(s) && t(s)(...h)) 637 | }, [ 638 | t(e).video.muted ? (u(), f("svg", pt, gt)) : (u(), f("svg", bt, Mt)) 639 | ], 8, ft), 640 | a("div", Tt, [ 641 | a("div", { 642 | class: "cvp-controller-volume-area", 643 | onMousedown: c[2] || (c[2] = we((h) => { 644 | t(e).container.mouseDown = !0, t(d)(h); 645 | }, ["self"])), 646 | onMousemove: c[3] || (c[3] = (...h) => t(d) && t(d)(...h)), 647 | onMouseup: c[4] || (c[4] = (h) => t(e).container.mouseDown = !1), 648 | onMouseleave: c[5] || (c[5] = (h) => t(e).container.mouseDown = !1) 649 | }, [ 650 | a("div", { 651 | class: "cvp-controller-volume-bar", 652 | style: G({ height: `${t(e).video.volume * 100}%` }) 653 | }, null, 4) 654 | ], 32) 655 | ]) 656 | ]), 657 | a("button", { 658 | class: "cvp-controller-button", 659 | title: "Backward", 660 | onMousedown: c[6] || (c[6] = (h) => { 661 | t(e).container.mouseDown = !0, t(y)(!1); 662 | }), 663 | onMouseup: c[7] || (c[7] = (h) => { 664 | t(e).container.mouseDown = !1, t(y)(!1); 665 | }) 666 | }, Et, 32), 667 | a("button", { 668 | class: "cvp-controller-button", 669 | title: "Forward", 670 | onMousedown: c[8] || (c[8] = (h) => { 671 | t(e).container.mouseDown = !0, t(y)(!0); 672 | }), 673 | onMouseup: c[9] || (c[9] = (h) => { 674 | t(e).container.mouseDown = !1, t(y)(!0); 675 | }) 676 | }, Vt, 32), 677 | Ct, 678 | a("div", Ft, [ 679 | a("div", Bt, "x" + C(((I = t(e).video.playbackRate) == null ? void 0 : I.toFixed(1)) || "1.0"), 1), 680 | a("ul", Dt, [ 681 | (u(!0), f(Ce, null, Fe([0.1, 0.5, 1, 1.5, 2, 5], (h) => (u(), f("li", Ht, [ 682 | a("button", { 683 | class: "cvp-controller-playback-rate-button", 684 | onClick: (Y) => t(V)(h) 685 | }, C(h.toFixed(1)), 9, zt) 686 | ]))), 256)) 687 | ]) 688 | ]), 689 | t(m) ? (u(), f("button", { 690 | key: 0, 691 | class: "cvp-controller-button", 692 | "data-active": t(e).range.enabled, 693 | title: t(e).range.enabled ? "Reset range" : "Set range", 694 | onClick: P 695 | }, [ 696 | t(e).range.enabled ? (u(), f("svg", Rt, It)) : (u(), f("svg", Lt, Nt)) 697 | ], 8, Pt)) : W("", !0), 698 | a("button", { 699 | class: "cvp-controller-button", 700 | "data-active": t(e).video.loop, 701 | title: t(e).video.loop ? "Play once" : "Repeat play", 702 | onClick: c[10] || (c[10] = (...h) => t(T) && t(T)(...h)) 703 | }, qt, 8, jt), 704 | t(x) && t(E) ? (u(), f("button", { 705 | key: 1, 706 | class: "cvp-controller-button", 707 | "data-active": t(e).bbox.enabled, 708 | title: t(e).bbox.enabled ? "Hide bounding box" : "Show bounding box", 709 | onClick: c[11] || (c[11] = (...h) => t($) && t($)(...h)) 710 | }, Kt, 8, Ut)) : W("", !0), 711 | a("button", { 712 | class: "cvp-controller-button", 713 | title: t(e).container.fullScreen ? "Normal screen" : "Full screen", 714 | onClick: c[12] || (c[12] = (...h) => t(p) && t(p)(...h)) 715 | }, [ 716 | t(e).container.fullScreen ? (u(), f("svg", Qt, Zt)) : (u(), f("svg", Xt, to)) 717 | ], 8, Jt) 718 | ]); 719 | }; 720 | } 721 | }, no = /* @__PURE__ */ O(oo, [["__scopeId", "data-v-f2163948"]]); 722 | const ao = ["data-active"], so = { 723 | __name: "index", 724 | setup(l) { 725 | const { data: e, handleContainerMouseMove: v } = L(); 726 | return (s, d) => (u(), f("div", { 727 | class: "cvp-footer", 728 | "data-active": t(e).container.mouseMove, 729 | onMouseenter: d[0] || (d[0] = (y) => t(e).container.mouseHold = !0), 730 | onMouseleave: d[1] || (d[1] = (y) => { 731 | t(e).container.mouseHold = !1, t(v)(); 732 | }) 733 | }, [ 734 | q(et), 735 | q(no) 736 | ], 40, ao)); 737 | } 738 | }, ro = /* @__PURE__ */ O(so, [["__scopeId", "data-v-33a4d776"]]); 739 | const lo = { class: "cvp-message" }, io = ["data-visible", "innerHTML"], co = { 740 | __name: "Message", 741 | setup(l) { 742 | const { data: e } = L(); 743 | return (v, s) => (u(), f("div", lo, [ 744 | a("div", { 745 | class: "cvp-message-text", 746 | "data-visible": t(e).message.visible, 747 | innerHTML: t(e).message.text 748 | }, null, 8, io) 749 | ])); 750 | } 751 | }, vo = /* @__PURE__ */ O(co, [["__scopeId", "data-v-8fa62035"]]); 752 | const uo = ["data-dark-mode", "data-type"], fo = { 753 | key: 0, 754 | class: "cvp-block" 755 | }, po = { 756 | __name: "Vue3CanvasVideoPlayer", 757 | props: { 758 | src: { type: String, default: "", required: !0 }, 759 | muted: { type: Boolean, default: !1 }, 760 | autoplay: { type: Boolean, default: !1 }, 761 | loop: { type: Boolean, default: !1 }, 762 | range: { type: Array, validator: (l) => !l.length || l.length === 2 && l.every((e) => typeof e == "number"), default: () => [0, 0] }, 763 | fps: { type: Number, default: 0 }, 764 | bbox: { type: Object, default: () => ({ data: {}, textColor: "#fff", fillColor: "rgba(0, 0, 255, 0.5)", borderSize: 1, borderColor: "rgba(255, 0, 0, 0.5)" }) }, 765 | type: { type: String, default: "overlay" }, 766 | messageTime: { type: Number, default: 1e3 }, 767 | preview: { type: Boolean, default: !1 }, 768 | darkMode: { type: Boolean, default: !0 } 769 | }, 770 | setup(l) { 771 | const e = l, v = J(null), { data: s, setVideoRange: d, handleContainerMouseMove: y } = L(); 772 | return Be(() => e, ({ src: V, muted: M, loop: T, range: $, fps: p, bbox: m, type: x, messageTime: E, preview: P }) => { 773 | Object.assign(s.container, { type: x }), Object.assign(s.video, { src: V, muted: M, loop: T, fps: p }), Object.assign(s.preview, { enabled: P }), Object.assign(s.range, { start: $[0], end: $[1] }), Object.assign(s.bbox, { ...m }), Object.assign(s.message, { time: E }), s.range.start && s.range.end && d(!0), Object.keys(s.bbox.data).length && (s.bbox.enabled = !0); 774 | }, { deep: !0 }), ge(() => { 775 | Object.assign(s, { container: { ...s.container, element: v.value } }), s.container.type === "overlay" && s.container.element.addEventListener("mousemove", y); 776 | }), De(() => { 777 | s.container.type === "overlay" && s.container.element.removeEventListener("mousemove", y); 778 | }), (V, M) => (u(), f("div", { 779 | id: "vue3-canvas-video-player", 780 | ref_key: "container", 781 | ref: v, 782 | "data-dark-mode": l.darkMode, 783 | "data-type": e.type 784 | }, [ 785 | q(Ae), 786 | q(Ke, { 787 | src: e.src, 788 | muted: e.muted, 789 | autoplay: e.autoplay 790 | }, null, 8, ["src", "muted", "autoplay"]), 791 | q(ro), 792 | q(vo), 793 | e.src.length ? W("", !0) : (u(), f("div", fo, C(t(s).block.text), 1)) 794 | ], 8, uo)); 795 | } 796 | }, ho = /* @__PURE__ */ O(po, [["__scopeId", "data-v-bd75ab9b"]]); 797 | export { 798 | ho as default 799 | }; 800 | -------------------------------------------------------------------------------- /dist/vue3-canvas-video-player.umd.cjs: -------------------------------------------------------------------------------- 1 | (function(e,$){typeof exports=="object"&&typeof module<"u"?module.exports=$(require("vue")):typeof define=="function"&&define.amd?define(["vue"],$):(e=typeof globalThis<"u"?globalThis:e||self,e.Vue3CanvasVideoPlayer=$(e.Vue))})(this,function(e){"use strict";const $=s=>{const t=parseInt(s),c=Math.floor(t/3600),a=Math.floor(t%3600/60),i=t%60;return`${c<10?"0"+c:c}:${a<10?"0"+a:a}:${i<10?"0"+i:i}`},N=(s,t)=>Math.round(s*t),Z=(s,t)=>s/t,L=s=>s===void 0?!1:new Intl.NumberFormat().format(s),o=e.reactive({container:{element:null,fullScreen:!1,type:"overlay",mouseDown:!1,mouseMove:!1,mouseHold:!1,resizeObserver:null},video:{element:null,src:"",canvas:null,width:0,height:0,duration:0,currentTime:0,muted:!1,volume:1,loop:!1,playbackRate:1,paused:!0,fps:0},progress:{seekWidth:0,seekTotal:0,bufferWidth:0},preview:{enabled:!1,element:null,left:0,time:"00:00:00"},range:{start:0,end:0,left:0,width:0,enabled:!1},bbox:{data:{},textColor:"#fff",fillColor:"rgba(0, 0, 255, 0.5)",borderSize:1,borderColor:"rgba(255, 0, 0, 0.5)",enabled:!1},message:{time:1e3,visible:!1,text:""},block:{text:"Video file has not been loaded"}});let O=!1;function F(){const s=e.computed(()=>o.range.start>0&&o.range.end>0),t=e.computed(()=>o.video.fps>0),c=e.computed(()=>Object.keys(o.bbox.data).length>0),a=()=>{const{video:{element:n,canvas:r,width:l,height:h,duration:g,fps:w},range:{start:S}}=o,_=r.getContext("2d"),q=(T,G)=>{const K="...",P=_.measureText(K).width+10;let M=T,R=_.measureText(M).width;if(R<=G||R<=P)return M;{let J=M.length;for(;R>=G-P&&J-- >0;)M=M.substr(0,J),R=_.measureText(M).width;return M+K}};r.width=l,r.height=h,n.loop=o.video.loop,s.value&&(n.currentTime=S),n.paused&&(o.video.paused=!0,n.pause());const U=()=>{const{video:{loop:T,src:G},range:{enabled:K,start:P,end:M},bbox:{data:R,textColor:J,fillColor:gt,borderSize:W,borderColor:bt,enabled:vt}}=o;if(!G){_.clearRect(0,0,l,h);return}_.drawImage(n,0,0,l,h);let Q=o.video.currentTime=n.currentTime;if(s.value&&K?(n.currentTime>=M&&(T?n.currentTime=P:(n.currentTime=M,n.pause(),o.video.paused=!0)),o.progress.seekWidth=(Q-P)/(M-P)*100,o.progress.bufferWidth=100):(n.ended&&T&&(Q=0),o.progress.seekWidth=Q/g*100,n.buffered.length>0&&(o.progress.bufferWidth=n.buffered.end(n.buffered.length-1)/g*100)),t.value&&c.value&&vt){const yt=N(Q,w),Y=R[yt];Y!=null&&Y.length&&Y.forEach(({label:se,xywh:D})=>{if(D&&D.length===4){const ee=D[0],te=D[1],oe=D[2]-D[0],le=D[3]-D[1];_.fillStyle=gt,_.fillRect(ee,te,oe,le),se&&(_.font="14px Roboto",_.fillStyle=J,_.fillText(q(se,oe),ee+5,te+15)),W&&(_.lineWidth=W,_.strokeStyle=bt,_.strokeRect(ee-W/2,te-W/2,oe+W,le+W))}})}n.paused&&Math.floor(o.progress.seekWidth)===100&&(o.video.paused=!0),window.requestAnimationFrame(U)};U()},i=n=>{const{video:{element:r,duration:l,fps:h},range:{enabled:g,start:w,end:S}}=o;r.currentTime=s.value&&g?Math.max(Math.min(n,S),w):Math.max(Math.min(n,l),0);const _=s.value&&g?Math.round((r.currentTime-w)/(S-w)*100):Math.round(r.currentTime/l*100);B(`Seek ${$(r.currentTime)} ${t.value?`[${L(N(r.currentTime,h))}]`:""} (${_}%)`)},u=({offsetX:n})=>{const{container:{mouseDown:r},video:{element:l,duration:h},progress:{seekTotal:g},preview:{enabled:w,element:S},range:{enabled:_,start:q,end:U}}=o;if(!l)return;const T=_?n/g*(U-q)+q:n/g*h;T===1/0||isNaN(T)||(r&&i(T),S&&w&&(S.currentTime=T,o.preview.time=$(T),n<65?o.preview.left=65:n>g-65?o.preview.left=g-65:o.preview.left=n))};let x;const y=({detail:n})=>{if(n===1){const{video:{element:r,paused:l},range:{enabled:h,start:g,end:w}}=o;x=setTimeout(()=>{l?(s.value&&h&&r.currentTime===w&&(r.currentTime=g),r.play()):r.pause(),B(l?"Play":"Pause"),o.video.paused=!l},250)}else n===2&&(clearTimeout(x),ne())},k=n=>{const{video:{element:r}}=o,l=Math.max(Math.min(n,1),0);r.volume=o.video.volume=l,B(`Volume ${Math.floor(l*100)}%`)},E=({offsetY:n})=>{const{container:{mouseDown:r}}=o;r&&k((100-n)/100)},f=()=>{const{video:{element:n,muted:r}}=o;n.muted=o.video.muted=!r,B(r?"Unmuted":"Muted")},p=()=>{const{video:{element:n,fps:r},range:{end:l}}=o;if(t.value){const h=N(n.currentTime,r)+1;N(l,r)===h?i(l):i(Z(h,r))}else i(n.currentTime+1)},b=()=>{const{video:{element:n,fps:r},range:{start:l}}=o;if(t.value){const h=N(n.currentTime,r)-1;N(l,r)===h?i(l):i(Z(h,r))}else i(n.currentTime-1)};let V;const C=n=>{const{container:{mouseDown:r}}=o;r?V=setInterval(()=>{n?p():b()},60):clearInterval(V)},I=n=>{const{video:{element:r}}=o;r.playbackRate=o.video.playbackRate=n,B(`Playback Rate x${n.toFixed(1)}`)},d=n=>{const{video:{element:r,duration:l},progress:{seekTotal:h},range:{start:g,end:w}}=o;o.range.enabled!==n&&(o.range.enabled=n,B(n?"Active range":"Inactive range")),n?(r.currentTime=g,o.range.left=0,o.range.width=h,o.progress.seekWidth=0):(r.currentTime=0,o.range.left=Math.floor(g/l*h),o.range.width=Math.ceil(w/l*h-g/l*h)||1)},H=()=>{const{video:{element:n,loop:r}}=o;n.loop=o.video.loop=!r,B(r?"Play once":"Repeat play")},m=()=>{const{bbox:{enabled:n}}=o;o.bbox.enabled=!n,B(n?"Hide bounding box":"Show bounding box")};let A;const B=n=>{const{message:r}=o,{time:l}=r;!l||(r.text=n,r.visible=!0,A&&clearTimeout(A),A=setTimeout(()=>{r.visible=!1},l))},ne=()=>{const{container:{element:n}}=o;document.fullscreenElement?document.exitFullscreen():n.requestFullscreen()},ae=({altKey:n,ctrlKey:r,key:l})=>{const{video:{element:h,paused:g,volume:w,fps:S}}=o;if(n&&r){if(l==="g"&&t.value){const _=window.prompt("Go to frame number",N(h.currentTime,S));g&&h.pause(),h.currentTime=Z(_,S)}l==="ArrowUp"&&k(w+.05),l==="ArrowDown"&&k(w-.05),l==="ArrowLeft"&&b(),l==="ArrowRight"&&p()}},re=()=>{o.container.fullScreen=Boolean(document.fullscreenElement),B(`${o.container.fullScreen?"Full":"Normal"} screen`)},_t=()=>{const{container:{element:n},range:{enabled:r},preview:{enabled:l}}=o;o.progress.seekTotal=n.offsetWidth,s.value&&r&&d(!0),l&&(o.preview.left=0)};let X;const ut=()=>{X&&clearTimeout(X),o.container.mouseMove=!0,!o.container.mouseHold&&(X=setTimeout(()=>{o.container.mouseMove=!1},2e3))};return e.onMounted(()=>{O||(window.addEventListener("keydown",ae),document.addEventListener("fullscreenchange",re),setTimeout(()=>{o.container.resizeObserver=new ResizeObserver(_t),o.container.resizeObserver.observe(o.container.element)},300),O=!0)}),e.onUnmounted(()=>{!O||(o.container.resizeObserver.unobserve(o.container.element),window.removeEventListener("keydown",ae),document.removeEventListener("fullscreenchange",re),O=!1)}),{data:o,initialVideo:a,setVideoSeek:u,toggleVideoPlay:y,toggleVideoMute:f,changeVideoVolume:E,changeVideoFrame:C,setVideoPlaybackRate:I,setVideoRange:d,toggleVideoLoop:H,toggleVideoBbox:m,toggleFullScreen:ne,setMessage:B,handleContainerMouseMove:ut}}const kt="",z=(s,t)=>{const c=s.__vccOpts||s;for(const[a,i]of t)c[a]=i;return c},j=s=>(e.pushScopeId("data-v-f43b31fa"),s=s(),e.popScopeId(),s),ie={class:"cvp-header"},de=["data-active"],ce=j(()=>e.createElementVNode("span",{style:{opacity:"0.5"}}," / ",-1)),fe={key:0},pe=j(()=>e.createElementVNode("span",{style:{opacity:"0.5"}}," / ",-1)),me=["data-active"],he=j(()=>e.createElementVNode("span",{style:{opacity:"0.5"}}," / ",-1)),_e={key:0},ue=j(()=>e.createElementVNode("span",{style:{opacity:"0.5"}}," / ",-1)),ge=z({__name:"Header",setup(s){const{data:t}=F(),c=e.computed(()=>t.range.start>0&&t.range.end>0),a=e.computed(()=>t.video.fps>0),i=e.computed(()=>$(t.video.src&&t.video.currentTime||0)),u=e.computed(()=>$(t.video.src&&t.video.duration||0)),x=e.computed(()=>$(t.video.src&&t.range.end||0)),y=e.computed(()=>L(N(t.video.src&&t.video.currentTime||0,t.video.fps))),k=e.computed(()=>L(N(t.video.src&&t.video.duration||0,t.video.fps))),E=e.computed(()=>L(N(t.video.src&&t.range.end||0,t.video.fps)));return(f,p)=>(e.openBlock(),e.createElementBlock("div",ie,[e.createElementVNode("div",{class:"cvp-information","data-active":!e.unref(t).range.enabled},[e.createElementVNode("span",null,e.toDisplayString(e.unref(i)),1),ce,e.createElementVNode("span",null,e.toDisplayString(e.unref(u)),1),e.unref(a)?(e.openBlock(),e.createElementBlock("span",fe,[e.createElementVNode("span",null," [ "+e.toDisplayString(e.unref(y)),1),pe,e.createElementVNode("span",null,e.toDisplayString(e.unref(k))+" ]",1)])):e.createCommentVNode("",!0)],8,de),e.unref(c)?(e.openBlock(),e.createElementBlock("div",{key:0,class:"cvp-information","data-active":e.unref(t).range.enabled},[e.createElementVNode("span",null,e.toDisplayString(e.unref(i)),1),he,e.createElementVNode("span",null,e.toDisplayString(e.unref(x)),1),e.unref(a)?(e.openBlock(),e.createElementBlock("span",_e,[e.createElementVNode("span",null," [ "+e.toDisplayString(e.unref(y)),1),ue,e.createElementVNode("span",null,e.toDisplayString(e.unref(E))+" ]",1)])):e.createCommentVNode("",!0)],8,me)):e.createCommentVNode("",!0)]))}},[["__scopeId","data-v-f43b31fa"]]),Vt="",be={class:"cvp-main"},ve=["src","muted","autoplay"],ye=z({__name:"Main",props:{src:{type:String,default:!1,required:!0},muted:{type:Boolean,default:!1},autoplay:{type:Boolean,default:!1}},emits:["loadedmetadata","play","pause","timeupdate","volumechange","error"],setup(s,{emit:t}){const c=s,a=e.ref(null),i=e.ref(null),{data:u,initialVideo:x,toggleVideoPlay:y}=F(),k=E=>{Object.assign(u,{video:{...u.video,element:a.value,canvas:i.value,width:a.value.videoWidth,height:a.value.videoHeight,duration:a.value.duration,paused:!(c.muted===!0&&c.autoplay===!0)}}),x(),t("loadedmetadata",E)};return(E,f)=>(e.openBlock(),e.createElementBlock("div",be,[e.createElementVNode("video",{class:"cvp-video",ref_key:"video",ref:a,src:s.src,muted:s.muted,autoplay:s.autoplay,onLoadedmetadata:f[0]||(f[0]=p=>k(p)),onPlay:f[1]||(f[1]=p=>t("play",p)),onPause:f[2]||(f[2]=p=>t("pause",p)),onTimeupdate:f[3]||(f[3]=p=>t("timeupdate",p)),onVolumechange:f[4]||(f[4]=p=>t("volumechange",p)),onError:f[5]||(f[5]=p=>t("error",p))},null,40,ve),e.createElementVNode("canvas",{class:"cvp-canvas",ref_key:"canvas",ref:i,onClick:f[6]||(f[6]=(...p)=>e.unref(y)&&e.unref(y)(...p))},null,512)]))}},[["__scopeId","data-v-352dd94f"]]),xt="",ke={class:"cvp-progress"},Ee={class:"cvp-progress-drag"},Ve=["src"],we={class:"cvp-progress-preview-time"},xe=z({__name:"Progress",setup(s){const t=e.ref(null),c=e.ref(null),{data:a,setVideoSeek:i}=F(),u=e.computed(()=>a.range.start>0&&a.range.end>0),x=e.computed(()=>`${a.video.src&&a.progress.bufferWidth||0}%`),y=e.computed(()=>`${a.video.src&&a.progress.seekWidth||0}%`),k=e.computed(()=>`${a.video.src&&a.range.left||0}px`),E=e.computed(()=>`${a.video.src&&a.range.width||0}px`),f=()=>{setTimeout(()=>{const{video:{width:p,height:b},preview:{enabled:V}}=a;if(!V)return;const C=c.value.getContext("2d"),I=p*.3,d=b*.3;c.value.width=I,c.value.height=d,Object.assign(a,{preview:{...a.preview,element:t.value}});const H=()=>{!t.value||(C.imageSmoothingEnabled=!0,C.drawImage(t.value,0,0,I,d),window.requestAnimationFrame(H))};H()},100)};return(p,b)=>(e.openBlock(),e.createElementBlock("div",ke,[e.createElementVNode("div",Ee,[e.createElementVNode("div",{class:"cvp-progress-area",onMousedown:b[0]||(b[0]=e.withModifiers(V=>{e.unref(a).container.mouseDown=!0,e.unref(i)(V)},["self"])),onMousemove:b[1]||(b[1]=(...V)=>e.unref(i)&&e.unref(i)(...V)),onMouseup:b[2]||(b[2]=V=>e.unref(a).container.mouseDown=!1),onMouseleave:b[3]||(b[3]=V=>e.unref(a).container.mouseDown=!1)},[e.createElementVNode("div",{class:"cvp-progress-buffer",style:e.normalizeStyle({width:e.unref(x)})},null,4),e.createElementVNode("div",{class:"cvp-progress-bar",style:e.normalizeStyle({width:e.unref(y)})},null,4),e.unref(u)?(e.openBlock(),e.createElementBlock("div",{key:0,class:"cvp-progress-range",style:e.normalizeStyle({left:e.unref(k),width:e.unref(E)})},null,4)):e.createCommentVNode("",!0)],32)]),e.unref(a).preview.enabled?(e.openBlock(),e.createElementBlock("div",{key:0,class:"cvp-progress-preview",style:e.normalizeStyle({left:`${e.unref(a).preview.left}px`})},[e.createElementVNode("video",{class:"cvp-progress-preview-video",ref_key:"video",ref:t,src:e.unref(a).video.src,onLoadeddata:f},null,40,Ve),e.createElementVNode("canvas",{class:"cvp-progress-preview-canvas",ref_key:"canvas",ref:c},null,512),e.createElementVNode("div",we,e.toDisplayString(e.unref(a).preview.time),1)],4)):e.createCommentVNode("",!0)]))}},[["__scopeId","data-v-c1f46be8"]]),Nt="",v=s=>(e.pushScopeId("data-v-f2163948"),s=s(),e.popScopeId(),s),Me={class:"cvp-controller"},Ne=["title"],Be={key:0,class:"cvp-controller-icon",viewBox:"-2 -2 28 28","stroke-width":"1",stroke:"#ffffff",fill:"none"},Se=[v(()=>e.createElementVNode("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"},null,-1)),v(()=>e.createElementVNode("path",{d:"M7 4v16l13 -8z"},null,-1))],Te={key:1,class:"cvp-controller-icon",viewBox:"0 0 24 24","stroke-width":"1",stroke:"#ffffff",fill:"none"},$e=[v(()=>e.createElementVNode("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"},null,-1)),v(()=>e.createElementVNode("rect",{x:"6",y:"5",width:"4",height:"14",rx:"1"},null,-1)),v(()=>e.createElementVNode("rect",{x:"14",y:"5",width:"4",height:"14",rx:"1"},null,-1))],Ce={class:"cvp-controller-volume"},De=["title"],Fe={key:0,class:"cvp-controller-icon",viewBox:"-2 -2 28 28","stroke-width":"1",stroke:"#ffffff",fill:"none"},ze=[v(()=>e.createElementVNode("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"},null,-1)),v(()=>e.createElementVNode("path",{d:"M6 15h-2a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h2l3.5 -4.5a0.8 .8 0 0 1 1.5 .5v14a0.8 .8 0 0 1 -1.5 .5l-3.5 -4.5"},null,-1)),v(()=>e.createElementVNode("path",{d:"M16 10l4 4m0 -4l-4 4"},null,-1))],He={key:1,class:"cvp-controller-icon",viewBox:"-2 -2 28 28","stroke-width":"1",stroke:"#ffffff",fill:"none"},Pe=[v(()=>e.createElementVNode("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"},null,-1)),v(()=>e.createElementVNode("path",{d:"M15 8a5 5 0 0 1 0 8"},null,-1)),v(()=>e.createElementVNode("path",{d:"M17.7 5a9 9 0 0 1 0 14"},null,-1)),v(()=>e.createElementVNode("path",{d:"M6 15h-2a1 1 0 0 1 -1 -1v-4a1 1 0 0 1 1 -1h2l3.5 -4.5a0.8 .8 0 0 1 1.5 .5v14a0.8 .8 0 0 1 -1.5 .5l-3.5 -4.5"},null,-1))],Re={class:"cvp-controller-volume-drag"},We=[v(()=>e.createElementVNode("svg",{class:"cvp-controller-icon",viewBox:"-1 -1 26 26","stroke-width":"1",stroke:"#ffffff",fill:"none"},[e.createElementVNode("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),e.createElementVNode("path",{d:"M20 5v14l-12 -7z"}),e.createElementVNode("line",{x1:"4",y1:"5",x2:"4",y2:"19"})],-1))],Ie=[v(()=>e.createElementVNode("svg",{class:"cvp-controller-icon",viewBox:"-1 -1 26 26","stroke-width":"1",stroke:"#ffffff",fill:"none"},[e.createElementVNode("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),e.createElementVNode("path",{d:"M4 5v14l12 -7z"}),e.createElementVNode("line",{x1:"20",y1:"5",x2:"20",y2:"19"})],-1))],Le=v(()=>e.createElementVNode("div",{style:{flex:"1"}},null,-1)),Oe={class:"cvp-controller-playback-rate"},je={class:"cvp-controller-playback-rate-text",title:"Playback rate"},Ae={class:"cvp-controller-playback-rate-list"},qe={class:"cvp-controller-playback-rate-item"},Ue=["onClick"],Ge=["data-active","title"],Ke={key:0,class:"cvp-controller-icon",viewBox:"-4 -4 32 32","stroke-width":"1",stroke:"#ffffff",fill:"none"},Je=[e.createStaticVNode('',5)],Qe={key:1,class:"cvp-controller-icon",viewBox:"-4 -4 32 32","stroke-width":"1",stroke:"#ffffff",fill:"none"},Ye=[e.createStaticVNode('',5)],Ze=["data-active","title"],Xe=[v(()=>e.createElementVNode("svg",{class:"cvp-controller-icon",viewBox:"-4 -4 32 32","stroke-width":"1.5",stroke:"#ffffff",fill:"none"},[e.createElementVNode("path",{stroke:"none",d:"M0 0h24v24H0z",fill:"none"}),e.createElementVNode("path",{d:"M4 12v-3a3 3 0 0 1 3 -3h13m-3 -3l3 3l-3 3"}),e.createElementVNode("path",{d:"M20 12v3a3 3 0 0 1 -3 3h-13m3 3l-3 -3l3 -3"})],-1))],et=["data-active","title"],tt=[e.createStaticVNode('',1)],ot=["title"],nt={key:0,class:"cvp-controller-icon",viewBox:"0 0 24 24","stroke-width":"1",stroke:"#ffffff",fill:"none"},at=[e.createStaticVNode('',5)],rt={key:1,class:"cvp-controller-icon",viewBox:"-2 -2 28 28","stroke-width":"1",stroke:"#ffffff",fill:"none"},st=[e.createStaticVNode('',5)],lt=z({__name:"Controller",setup(s){const{data:t,toggleVideoPlay:c,toggleVideoMute:a,changeVideoVolume:i,changeVideoFrame:u,setVideoPlaybackRate:x,setVideoRange:y,toggleVideoLoop:k,toggleVideoBbox:E,toggleFullScreen:f}=F(),p=e.computed(()=>t.range.start>0&&t.range.end>0),b=e.computed(()=>t.video.fps>0),V=e.computed(()=>Object.keys(t.bbox.data).length>0),C=()=>{y(!t.range.enabled)};return(I,d)=>{var H;return e.openBlock(),e.createElementBlock("div",Me,[e.createElementVNode("button",{class:"cvp-controller-button",title:e.unref(t).video.paused?"Play":"Pause",onClick:d[0]||(d[0]=(...m)=>e.unref(c)&&e.unref(c)(...m))},[e.unref(t).video.paused?(e.openBlock(),e.createElementBlock("svg",Be,Se)):(e.openBlock(),e.createElementBlock("svg",Te,$e))],8,Ne),e.createElementVNode("div",Ce,[e.createElementVNode("button",{class:"cvp-controller-button",title:e.unref(t).video.muted?"Unmute":"Mute",onClick:d[1]||(d[1]=(...m)=>e.unref(a)&&e.unref(a)(...m))},[e.unref(t).video.muted?(e.openBlock(),e.createElementBlock("svg",Fe,ze)):(e.openBlock(),e.createElementBlock("svg",He,Pe))],8,De),e.createElementVNode("div",Re,[e.createElementVNode("div",{class:"cvp-controller-volume-area",onMousedown:d[2]||(d[2]=e.withModifiers(m=>{e.unref(t).container.mouseDown=!0,e.unref(i)(m)},["self"])),onMousemove:d[3]||(d[3]=(...m)=>e.unref(i)&&e.unref(i)(...m)),onMouseup:d[4]||(d[4]=m=>e.unref(t).container.mouseDown=!1),onMouseleave:d[5]||(d[5]=m=>e.unref(t).container.mouseDown=!1)},[e.createElementVNode("div",{class:"cvp-controller-volume-bar",style:e.normalizeStyle({height:`${e.unref(t).video.volume*100}%`})},null,4)],32)])]),e.createElementVNode("button",{class:"cvp-controller-button",title:"Backward",onMousedown:d[6]||(d[6]=m=>{e.unref(t).container.mouseDown=!0,e.unref(u)(!1)}),onMouseup:d[7]||(d[7]=m=>{e.unref(t).container.mouseDown=!1,e.unref(u)(!1)})},We,32),e.createElementVNode("button",{class:"cvp-controller-button",title:"Forward",onMousedown:d[8]||(d[8]=m=>{e.unref(t).container.mouseDown=!0,e.unref(u)(!0)}),onMouseup:d[9]||(d[9]=m=>{e.unref(t).container.mouseDown=!1,e.unref(u)(!0)})},Ie,32),Le,e.createElementVNode("div",Oe,[e.createElementVNode("div",je,"x"+e.toDisplayString(((H=e.unref(t).video.playbackRate)==null?void 0:H.toFixed(1))||"1.0"),1),e.createElementVNode("ul",Ae,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList([.1,.5,1,1.5,2,5],m=>(e.openBlock(),e.createElementBlock("li",qe,[e.createElementVNode("button",{class:"cvp-controller-playback-rate-button",onClick:A=>e.unref(x)(m)},e.toDisplayString(m.toFixed(1)),9,Ue)]))),256))])]),e.unref(p)?(e.openBlock(),e.createElementBlock("button",{key:0,class:"cvp-controller-button","data-active":e.unref(t).range.enabled,title:e.unref(t).range.enabled?"Reset range":"Set range",onClick:C},[e.unref(t).range.enabled?(e.openBlock(),e.createElementBlock("svg",Ke,Je)):(e.openBlock(),e.createElementBlock("svg",Qe,Ye))],8,Ge)):e.createCommentVNode("",!0),e.createElementVNode("button",{class:"cvp-controller-button","data-active":e.unref(t).video.loop,title:e.unref(t).video.loop?"Play once":"Repeat play",onClick:d[10]||(d[10]=(...m)=>e.unref(k)&&e.unref(k)(...m))},Xe,8,Ze),e.unref(b)&&e.unref(V)?(e.openBlock(),e.createElementBlock("button",{key:1,class:"cvp-controller-button","data-active":e.unref(t).bbox.enabled,title:e.unref(t).bbox.enabled?"Hide bounding box":"Show bounding box",onClick:d[11]||(d[11]=(...m)=>e.unref(E)&&e.unref(E)(...m))},tt,8,et)):e.createCommentVNode("",!0),e.createElementVNode("button",{class:"cvp-controller-button",title:e.unref(t).container.fullScreen?"Normal screen":"Full screen",onClick:d[12]||(d[12]=(...m)=>e.unref(f)&&e.unref(f)(...m))},[e.unref(t).container.fullScreen?(e.openBlock(),e.createElementBlock("svg",nt,at)):(e.openBlock(),e.createElementBlock("svg",rt,st))],8,ot)])}}},[["__scopeId","data-v-f2163948"]]),Jt="",it=["data-active"],dt=z({__name:"index",setup(s){const{data:t,handleContainerMouseMove:c}=F();return(a,i)=>(e.openBlock(),e.createElementBlock("div",{class:"cvp-footer","data-active":e.unref(t).container.mouseMove,onMouseenter:i[0]||(i[0]=u=>e.unref(t).container.mouseHold=!0),onMouseleave:i[1]||(i[1]=u=>{e.unref(t).container.mouseHold=!1,e.unref(c)()})},[e.createVNode(xe),e.createVNode(lt)],40,it))}},[["__scopeId","data-v-33a4d776"]]),Yt="",ct={class:"cvp-message"},ft=["data-visible","innerHTML"],pt=z({__name:"Message",setup(s){const{data:t}=F();return(c,a)=>(e.openBlock(),e.createElementBlock("div",ct,[e.createElementVNode("div",{class:"cvp-message-text","data-visible":e.unref(t).message.visible,innerHTML:e.unref(t).message.text},null,8,ft)]))}},[["__scopeId","data-v-8fa62035"]]),Xt="",mt=["data-dark-mode","data-type"],ht={key:0,class:"cvp-block"};return z({__name:"Vue3CanvasVideoPlayer",props:{src:{type:String,default:"",required:!0},muted:{type:Boolean,default:!1},autoplay:{type:Boolean,default:!1},loop:{type:Boolean,default:!1},range:{type:Array,validator:s=>!s.length||s.length===2&&s.every(t=>typeof t=="number"),default:()=>[0,0]},fps:{type:Number,default:0},bbox:{type:Object,default:()=>({data:{},textColor:"#fff",fillColor:"rgba(0, 0, 255, 0.5)",borderSize:1,borderColor:"rgba(255, 0, 0, 0.5)"})},type:{type:String,default:"overlay"},messageTime:{type:Number,default:1e3},preview:{type:Boolean,default:!1},darkMode:{type:Boolean,default:!0}},setup(s){const t=s,c=e.ref(null),{data:a,setVideoRange:i,handleContainerMouseMove:u}=F();return e.watch(()=>t,({src:x,muted:y,loop:k,range:E,fps:f,bbox:p,type:b,messageTime:V,preview:C})=>{Object.assign(a.container,{type:b}),Object.assign(a.video,{src:x,muted:y,loop:k,fps:f}),Object.assign(a.preview,{enabled:C}),Object.assign(a.range,{start:E[0],end:E[1]}),Object.assign(a.bbox,{...p}),Object.assign(a.message,{time:V}),a.range.start&&a.range.end&&i(!0),Object.keys(a.bbox.data).length&&(a.bbox.enabled=!0)},{deep:!0}),e.onMounted(()=>{Object.assign(a,{container:{...a.container,element:c.value}}),a.container.type==="overlay"&&a.container.element.addEventListener("mousemove",u)}),e.onBeforeUnmount(()=>{a.container.type==="overlay"&&a.container.element.removeEventListener("mousemove",u)}),(x,y)=>(e.openBlock(),e.createElementBlock("div",{id:"vue3-canvas-video-player",ref_key:"container",ref:c,"data-dark-mode":s.darkMode,"data-type":t.type},[e.createVNode(ge),e.createVNode(ye,{src:t.src,muted:t.muted,autoplay:t.autoplay},null,8,["src","muted","autoplay"]),e.createVNode(dt),e.createVNode(pt),t.src.length?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("div",ht,e.toDisplayString(e.unref(a).block.text),1))],8,mt))}},[["__scopeId","data-v-bd75ab9b"]])}); 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue3CanvasVideoPlayer 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/Vue3CanvasVideoPlayer.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 83 | 84 | 187 | -------------------------------------------------------------------------------- /lib/components/Footer/Controller.vue: -------------------------------------------------------------------------------- 1 | 172 | 173 | 200 | 201 | 331 | -------------------------------------------------------------------------------- /lib/components/Footer/Progress.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 79 | 80 | 184 | -------------------------------------------------------------------------------- /lib/components/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 38 | -------------------------------------------------------------------------------- /lib/components/Header.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 44 | 45 | 72 | -------------------------------------------------------------------------------- /lib/components/Main.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 71 | 72 | 90 | -------------------------------------------------------------------------------- /lib/components/Message.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | 18 | 50 | -------------------------------------------------------------------------------- /lib/compositions/player.js: -------------------------------------------------------------------------------- 1 | import { reactive, computed, onMounted, onUnmounted } from 'vue'; 2 | import { secondToTimeString, secondToFrameNumber, frameNumberToSecond, numericWithComma } from '../utils'; 3 | 4 | const data = reactive({ 5 | container: { 6 | element: null, 7 | fullScreen: false, 8 | type: 'overlay', 9 | mouseDown: false, 10 | mouseMove: false, 11 | mouseHold: false, 12 | resizeObserver: null, 13 | }, 14 | video: { 15 | element: null, 16 | src: '', 17 | canvas: null, 18 | width: 0, 19 | height: 0, 20 | duration: 0, 21 | currentTime: 0, 22 | muted: false, 23 | volume: 1, 24 | loop: false, 25 | playbackRate: 1.0, 26 | paused: true, 27 | fps: 0, 28 | }, 29 | progress: { 30 | seekWidth: 0, 31 | seekTotal: 0, 32 | bufferWidth: 0, 33 | }, 34 | preview: { 35 | enabled: false, 36 | element: null, 37 | left: 0, 38 | time: '00:00:00', 39 | }, 40 | range: { 41 | start: 0, 42 | end: 0, 43 | left: 0, 44 | width: 0, 45 | enabled: false, 46 | }, 47 | bbox: { 48 | data: {}, 49 | textColor: '#fff', 50 | fillColor: 'rgba(0, 0, 255, 0.5)', 51 | borderSize: 1, 52 | borderColor: 'rgba(255, 0, 0, 0.5)', 53 | enabled: false, 54 | }, 55 | message: { 56 | time: 1000, 57 | visible: false, 58 | text: '', 59 | }, 60 | block: { 61 | text: 'Video file has not been loaded', 62 | }, 63 | }); 64 | 65 | let mounted = false; 66 | 67 | export default function() { 68 | // 컴퓨티드 69 | const hasRange = computed(() => (data.range.start > 0) && (data.range.end > 0)); 70 | const hasFps = computed(() => data.video.fps > 0); 71 | const hasBbox = computed(() => Object.keys(data.bbox.data).length > 0); 72 | 73 | // 비디오 초기화 74 | const initialVideo = () => { 75 | const { 76 | video: { element: videoElement, canvas: videoCanvas, width: videoWidth, height: videoHeight, duration: videoDuration, fps: videoFps }, 77 | range: { start: rangeStart }, 78 | } = data; 79 | 80 | const context = videoCanvas.getContext('2d'); 81 | 82 | const ellipsisText = (string, width) => { 83 | const ellipsis = '...'; 84 | const ellipsisWidth = context.measureText(ellipsis).width + 10; 85 | 86 | let text = string; 87 | let textWidth = context.measureText(text).width; 88 | 89 | if (textWidth <= width || textWidth <= ellipsisWidth) { 90 | return text; 91 | } else { 92 | let length = text.length; 93 | 94 | while (textWidth >= width - ellipsisWidth && length-- > 0) { 95 | text = text.substr(0, length); 96 | textWidth = context.measureText(text).width; 97 | } 98 | 99 | return text + ellipsis; 100 | } 101 | }; 102 | 103 | videoCanvas.width = videoWidth; 104 | videoCanvas.height = videoHeight; 105 | 106 | videoElement.loop = data.video.loop; 107 | 108 | if (hasRange.value) { 109 | videoElement.currentTime = rangeStart; 110 | } 111 | 112 | if (videoElement.paused) { 113 | data.video.paused = true; 114 | videoElement.pause(); 115 | } 116 | 117 | const drawCanvas = () => { 118 | const { 119 | video: { loop: videoLoop, src: videoSrc }, 120 | range: { enabled: rangeEnabled, start: rangeStart, end: rangeEnd }, 121 | bbox: { data: bboxData, textColor: bboxTextColor, fillColor: bboxFillColor, borderSize: bboxBorderSize, borderColor: bboxBorderColor, enabled: bboxEnabled }, 122 | } = data; 123 | 124 | if (!videoSrc) { 125 | context.clearRect(0, 0, videoWidth, videoHeight); 126 | 127 | return; 128 | } 129 | 130 | context.drawImage(videoElement, 0, 0, videoWidth, videoHeight); 131 | 132 | let currentTime = data.video.currentTime = videoElement.currentTime; 133 | 134 | if (hasRange.value && rangeEnabled) { // 구간 있음 135 | if (videoElement.currentTime >= rangeEnd) { 136 | if (videoLoop) { 137 | videoElement.currentTime = rangeStart; 138 | } else { 139 | videoElement.currentTime = rangeEnd; 140 | videoElement.pause(); 141 | 142 | data.video.paused = true; 143 | } 144 | } 145 | 146 | data.progress.seekWidth = (((currentTime - rangeStart) / (rangeEnd - rangeStart)) * 100); 147 | data.progress.bufferWidth = 100; 148 | } else { // 구간 없음 149 | if (videoElement.ended && videoLoop) { 150 | currentTime = 0; 151 | } 152 | 153 | data.progress.seekWidth = ((currentTime / videoDuration) * 100); 154 | 155 | if (videoElement.buffered.length > 0) { 156 | data.progress.bufferWidth = (videoElement.buffered.end(videoElement.buffered.length - 1) / videoDuration) * 100; 157 | } 158 | } 159 | 160 | if (hasFps.value && hasBbox.value && bboxEnabled) { 161 | const frameNumber = secondToFrameNumber(currentTime, videoFps); 162 | const frame = bboxData[frameNumber]; 163 | 164 | if (frame?.length) { 165 | frame.forEach(({ label, xywh }) => { 166 | if (xywh && xywh.length === 4) { 167 | const x = xywh[0]; 168 | const y = xywh[1]; 169 | const w = xywh[2] - xywh[0]; 170 | const h = xywh[3] - xywh[1]; 171 | 172 | context.fillStyle = bboxFillColor; 173 | context.fillRect(x, y, w, h); 174 | 175 | if (label) { 176 | context.font = '14px Roboto'; 177 | context.fillStyle = bboxTextColor; 178 | context.fillText(ellipsisText(label, w), x + 5, y + 15); 179 | } 180 | 181 | if (bboxBorderSize) { 182 | context.lineWidth = bboxBorderSize; 183 | context.strokeStyle = bboxBorderColor; 184 | context.strokeRect(x - bboxBorderSize / 2, y - bboxBorderSize / 2, w + bboxBorderSize, h + bboxBorderSize); 185 | } 186 | } 187 | }); 188 | } 189 | } 190 | 191 | if (videoElement.paused && Math.floor(data.progress.seekWidth) === 100) { 192 | data.video.paused = true; 193 | } 194 | 195 | window.requestAnimationFrame(drawCanvas); 196 | }; 197 | 198 | drawCanvas(); 199 | }; 200 | 201 | // 프레임 설정 202 | const setFrame = (time) => { 203 | const { 204 | video: { element: videoElement, duration: videoDuration, fps: videoFps }, 205 | range: { enabled: rangeEnabled, start: rangeStart, end: rangeEnd }, 206 | } = data; 207 | 208 | videoElement.currentTime = (hasRange.value && rangeEnabled) ? Math.max(Math.min(time, rangeEnd), rangeStart) : Math.max(Math.min(time, videoDuration), 0); 209 | 210 | const percent = (hasRange.value && rangeEnabled) ? Math.round((videoElement.currentTime - rangeStart) / (rangeEnd - rangeStart) * 100) : Math.round(videoElement.currentTime / videoDuration * 100); 211 | 212 | setMessage(`Seek ${ 213 | secondToTimeString(videoElement.currentTime) 214 | } ${ 215 | (hasFps.value) ? `[${ numericWithComma(secondToFrameNumber(videoElement.currentTime, videoFps)) }]` : '' 216 | } (${ percent }%)`); 217 | }; 218 | 219 | // 탐색 220 | const setVideoSeek = ({ offsetX }) => { 221 | const { 222 | container: { mouseDown: containerMouseDown }, 223 | video: { element: videoElement, duration: videoDuration }, 224 | progress: { seekTotal }, 225 | preview: { enabled: previewEnabled, element: previewElement }, 226 | range: { enabled: rangeEnabled, start: rangeStart, end: rangeEnd }, 227 | } = data; 228 | 229 | if (!videoElement) return; 230 | 231 | const currentTime = (rangeEnabled) ? ((offsetX / seekTotal) * (rangeEnd - rangeStart)) + rangeStart : (offsetX / seekTotal) * videoDuration; 232 | 233 | if (currentTime === Infinity || isNaN(currentTime)) return; 234 | 235 | if (containerMouseDown) { 236 | setFrame(currentTime); 237 | } 238 | 239 | // 미리보기 240 | if (previewElement && previewEnabled) { 241 | previewElement.currentTime = currentTime; 242 | 243 | data.preview.time = secondToTimeString(currentTime); 244 | 245 | if (offsetX < 65) { 246 | data.preview.left = 65; 247 | } else if (offsetX > seekTotal - 65) { 248 | data.preview.left = seekTotal - 65; 249 | } else { 250 | data.preview.left = offsetX; 251 | } 252 | } 253 | } 254 | 255 | // 재생 / 일시 정지 256 | let toggleVideoPlayTimer; 257 | const toggleVideoPlay = ({ detail }) => { 258 | if (detail === 1) { 259 | const { 260 | video: { element: videoElement, paused: videoPaused }, 261 | range: { enabled: rangeEnabled, start: rangeStart, end: rangeEnd }, 262 | } = data; 263 | 264 | toggleVideoPlayTimer = setTimeout(() => { 265 | if (videoPaused) { 266 | if (hasRange.value && rangeEnabled && videoElement.currentTime === rangeEnd) { 267 | videoElement.currentTime = rangeStart; 268 | } 269 | 270 | videoElement.play(); 271 | } else { 272 | videoElement.pause(); 273 | } 274 | 275 | setMessage((videoPaused) ? 'Play' : 'Pause'); 276 | 277 | data.video.paused = !videoPaused; 278 | }, 250); 279 | } else if (detail === 2) { 280 | clearTimeout(toggleVideoPlayTimer); 281 | 282 | toggleFullScreen(); 283 | } 284 | }; 285 | 286 | // 소리 설정 287 | const setVolume = (value) => { 288 | const { 289 | video: { element: videoElement }, 290 | } = data; 291 | 292 | const volume = Math.max(Math.min(value, 1), 0); 293 | 294 | videoElement.volume = data.video.volume = volume; 295 | 296 | setMessage(`Volume ${ Math.floor(volume * 100) }%`); 297 | } 298 | 299 | // 볼륨 변경 300 | const changeVideoVolume = ({ offsetY }) => { 301 | const { 302 | container: { mouseDown: containerMouseDown }, 303 | } = data; 304 | 305 | if (containerMouseDown) { 306 | setVolume((100 - offsetY) / 100); 307 | } 308 | } 309 | 310 | // 볼륨 토글 311 | const toggleVideoMute = () => { 312 | const { 313 | video: { element: videoElement, muted: videoMuted }, 314 | } = data; 315 | 316 | videoElement.muted = data.video.muted = !videoMuted; 317 | 318 | setMessage((videoMuted) ? 'Unmuted' : 'Muted'); 319 | }; 320 | 321 | // 프레임 앞으로 322 | const forwardFrame = () => { 323 | const { 324 | video: { element: videoElement, fps: videoFps }, 325 | range: { end: rangeEnd }, 326 | } = data; 327 | 328 | if (hasFps.value) { 329 | const nextFrameNumber = secondToFrameNumber(videoElement.currentTime, videoFps) + 1; 330 | 331 | if (secondToFrameNumber(rangeEnd, videoFps) === nextFrameNumber) { 332 | setFrame(rangeEnd); 333 | } else { 334 | setFrame(frameNumberToSecond(nextFrameNumber, videoFps)); 335 | } 336 | } else { 337 | setFrame(videoElement.currentTime + 1); 338 | } 339 | }; 340 | 341 | // 프레임 뒤로 342 | const backwardFrame = () => { 343 | const { 344 | video: { element: videoElement, fps: videoFps }, 345 | range: { start: rangeStart }, 346 | } = data; 347 | 348 | if (hasFps.value) { 349 | const prevFrameNumber = secondToFrameNumber(videoElement.currentTime, videoFps) - 1; 350 | 351 | if (secondToFrameNumber(rangeStart, videoFps) === prevFrameNumber) { 352 | setFrame(rangeStart); 353 | } else { 354 | setFrame(frameNumberToSecond(prevFrameNumber, videoFps)); 355 | } 356 | } else { 357 | setFrame(videoElement.currentTime - 1); 358 | } 359 | }; 360 | 361 | // 프레임 변경 362 | let changeVideoFrameInterval; 363 | const changeVideoFrame = (forward) => { 364 | const { 365 | container: { mouseDown: containerMouseDown }, 366 | } = data; 367 | 368 | if (containerMouseDown) { 369 | changeVideoFrameInterval = setInterval(() => { 370 | if (forward) { 371 | forwardFrame(); 372 | } else { 373 | backwardFrame(); 374 | } 375 | }, 60); 376 | } else { 377 | clearInterval(changeVideoFrameInterval); 378 | } 379 | }; 380 | 381 | // 재생 속도 382 | const setVideoPlaybackRate = (value) => { 383 | const { 384 | video: { element: videoElement }, 385 | } = data; 386 | 387 | videoElement.playbackRate = data.video.playbackRate = value; 388 | 389 | setMessage(`Playback Rate x${ value.toFixed(1) }`); 390 | }; 391 | 392 | // 구간 393 | const setVideoRange = (enabled) => { 394 | const { 395 | video: { element: videoElement, duration: videoDuration }, 396 | progress: { seekTotal }, 397 | range: { start: rangeStart, end: rangeEnd }, 398 | } = data; 399 | 400 | if (data.range.enabled !== enabled) { 401 | data.range.enabled = enabled; 402 | setMessage((enabled) ? 'Active range' : 'Inactive range'); 403 | } 404 | 405 | if (enabled) { 406 | videoElement.currentTime = rangeStart; 407 | 408 | data.range.left = 0; 409 | data.range.width = seekTotal; 410 | data.progress.seekWidth = 0; 411 | } else { 412 | videoElement.currentTime = 0; 413 | 414 | data.range.left = Math.floor((rangeStart / videoDuration) * seekTotal); 415 | data.range.width = Math.ceil(((rangeEnd / videoDuration) * seekTotal) - ((rangeStart / videoDuration) * seekTotal)) || 1; 416 | } 417 | }; 418 | 419 | // 반복 420 | const toggleVideoLoop = () => { 421 | const { 422 | video: { element: videoElement, loop: videoLoop }, 423 | } = data; 424 | 425 | videoElement.loop = data.video.loop = !videoLoop; 426 | 427 | setMessage((videoLoop) ? 'Play once' : 'Repeat play'); 428 | }; 429 | 430 | // 바운딩 박스 431 | const toggleVideoBbox = () => { 432 | const { 433 | bbox: { enabled: bboxEnabled }, 434 | } = data; 435 | 436 | data.bbox.enabled = !bboxEnabled; 437 | 438 | setMessage((bboxEnabled) ? 'Hide bounding box' : 'Show bounding box'); 439 | }; 440 | 441 | // 메세지 442 | let setMessageTimer; 443 | const setMessage = (text) => { 444 | const { message } = data; 445 | const { time: messageTime } = message; 446 | 447 | if (!messageTime) return; 448 | 449 | message.text = text; 450 | message.visible = true; 451 | 452 | if (setMessageTimer) clearTimeout(setMessageTimer); 453 | 454 | setMessageTimer = setTimeout(() => { 455 | message.visible = false; 456 | }, messageTime); 457 | }; 458 | 459 | // 전체화면 / 일반 화면 460 | const toggleFullScreen = () => { 461 | const { 462 | container: { element: containerElement }, 463 | } = data; 464 | 465 | if (document.fullscreenElement) { 466 | document.exitFullscreen(); 467 | } else { 468 | containerElement.requestFullscreen(); 469 | } 470 | }; 471 | 472 | // 윈도우 키 다운 473 | const handleWindowKeydown = ({ altKey, ctrlKey, key }) => { 474 | const { 475 | video: { element: videoElement, paused: videoPaused, volume: videoVolume, fps: videoFps }, 476 | } = data; 477 | 478 | if (altKey && ctrlKey) { 479 | if (key === 'g' && hasFps.value) { // 프레임 이동 480 | const frameNumber = window.prompt('Go to frame number', secondToFrameNumber(videoElement.currentTime, videoFps)); 481 | 482 | if (videoPaused) { 483 | videoElement.pause(); 484 | } 485 | 486 | videoElement.currentTime = frameNumberToSecond(frameNumber, videoFps); 487 | } 488 | 489 | if (key === 'ArrowUp') { // 소리 증가 490 | setVolume(videoVolume + 0.05); 491 | } 492 | 493 | if (key === 'ArrowDown') { // 소리 감소 494 | setVolume(videoVolume - 0.05); 495 | } 496 | 497 | if (key === 'ArrowLeft') { // 프레임 뒤로 이동 498 | backwardFrame(); 499 | } 500 | 501 | if (key === 'ArrowRight') { // 프레임 앞으로 이동 502 | forwardFrame(); 503 | } 504 | } 505 | }; 506 | 507 | // 다큐먼트 화면 전환 508 | const handleDocumentFullScreenChange = () => { 509 | data.container.fullScreen = Boolean(document.fullscreenElement); 510 | 511 | setMessage(`${ (data.container.fullScreen) ? 'Full' : 'Normal' } screen`); 512 | }; 513 | 514 | // 컨테이너 리사이즈 515 | const handleContainerResize = () => { 516 | const { 517 | container: { element: containerElement }, 518 | range: { enabled: rangeEnabled }, 519 | preview: { enabled: previewEnabled }, 520 | } = data; 521 | 522 | data.progress.seekTotal = containerElement.offsetWidth; 523 | 524 | if (hasRange.value && rangeEnabled) { 525 | setVideoRange(true); 526 | } 527 | 528 | if (previewEnabled) { 529 | data.preview.left = 0; 530 | } 531 | }; 532 | 533 | // 컨테이너 마우스 이동 534 | let handleContainerMouseMoveTimer; 535 | const handleContainerMouseMove = () => { 536 | if (handleContainerMouseMoveTimer) clearTimeout(handleContainerMouseMoveTimer); 537 | 538 | data.container.mouseMove = true; 539 | 540 | if (data.container.mouseHold) return; 541 | 542 | handleContainerMouseMoveTimer = setTimeout(() => { 543 | data.container.mouseMove = false; 544 | }, 2000); 545 | }; 546 | 547 | // 마운트 548 | onMounted(() => { 549 | if (mounted) return; 550 | 551 | window.addEventListener('keydown', handleWindowKeydown); 552 | 553 | document.addEventListener('fullscreenchange', handleDocumentFullScreenChange); 554 | 555 | setTimeout(() => { 556 | data.container.resizeObserver = new ResizeObserver(handleContainerResize); 557 | data.container.resizeObserver.observe(data.container.element); 558 | 559 | }, 300); 560 | 561 | mounted = true; 562 | }); 563 | 564 | // 언마운트 565 | onUnmounted(() => { 566 | if (!mounted) return; 567 | 568 | data.container.resizeObserver.unobserve(data.container.element); 569 | 570 | window.removeEventListener('keydown', handleWindowKeydown); 571 | 572 | document.removeEventListener('fullscreenchange', handleDocumentFullScreenChange); 573 | 574 | mounted = false; 575 | }); 576 | 577 | // 반환 578 | return { 579 | data, 580 | initialVideo, 581 | setVideoSeek, 582 | toggleVideoPlay, 583 | toggleVideoMute, 584 | changeVideoVolume, 585 | changeVideoFrame, 586 | setVideoPlaybackRate, 587 | setVideoRange, 588 | toggleVideoLoop, 589 | toggleVideoBbox, 590 | toggleFullScreen, 591 | setMessage, 592 | handleContainerMouseMove, 593 | }; 594 | } 595 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | import Vue3CanvasVideoPlayer from './Vue3CanvasVideoPlayer.vue'; 2 | 3 | export default Vue3CanvasVideoPlayer; 4 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | export const secondToTimeString = (seconds) => { 2 | const sec = parseInt(seconds); 3 | const h = Math.floor(sec / 3600); 4 | const m = Math.floor((sec % 3600) / 60); 5 | const s = sec % 60; 6 | 7 | return `${ (h < 10) ? '0' + h : h }:${ (m < 10) ? '0' + m : m }:${ (s < 10) ? '0' + s : s }`; 8 | }; 9 | 10 | export const secondToFrameNumber = (seconds, fps) => Math.round(seconds * fps); 11 | 12 | export const frameNumberToSecond = (frameNumber, fps) => frameNumber / fps; 13 | 14 | export const numericWithComma = (value) => { 15 | if (value === undefined) return false; 16 | 17 | return new Intl.NumberFormat().format(value); 18 | }; 19 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-canvas-video-player", 3 | "version": "1.2.2", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "vue3-canvas-video-player", 9 | "version": "1.2.2", 10 | "license": "MIT", 11 | "dependencies": { 12 | "vue": "^3.2.41", 13 | "vue3-canvas-video-player": "^1.0.3" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-vue": "^3.2.0", 17 | "vite": "^3.2.3" 18 | } 19 | }, 20 | "node_modules/@babel/parser": { 21 | "version": "7.20.5", 22 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", 23 | "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", 24 | "bin": { 25 | "parser": "bin/babel-parser.js" 26 | }, 27 | "engines": { 28 | "node": ">=6.0.0" 29 | } 30 | }, 31 | "node_modules/@esbuild/android-arm": { 32 | "version": "0.15.18", 33 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", 34 | "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", 35 | "cpu": [ 36 | "arm" 37 | ], 38 | "dev": true, 39 | "optional": true, 40 | "os": [ 41 | "android" 42 | ], 43 | "engines": { 44 | "node": ">=12" 45 | } 46 | }, 47 | "node_modules/@esbuild/linux-loong64": { 48 | "version": "0.15.18", 49 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", 50 | "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", 51 | "cpu": [ 52 | "loong64" 53 | ], 54 | "dev": true, 55 | "optional": true, 56 | "os": [ 57 | "linux" 58 | ], 59 | "engines": { 60 | "node": ">=12" 61 | } 62 | }, 63 | "node_modules/@vitejs/plugin-vue": { 64 | "version": "3.2.0", 65 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz", 66 | "integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==", 67 | "dev": true, 68 | "engines": { 69 | "node": "^14.18.0 || >=16.0.0" 70 | }, 71 | "peerDependencies": { 72 | "vite": "^3.0.0", 73 | "vue": "^3.2.25" 74 | } 75 | }, 76 | "node_modules/@vue/compiler-core": { 77 | "version": "3.2.45", 78 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", 79 | "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", 80 | "dependencies": { 81 | "@babel/parser": "^7.16.4", 82 | "@vue/shared": "3.2.45", 83 | "estree-walker": "^2.0.2", 84 | "source-map": "^0.6.1" 85 | } 86 | }, 87 | "node_modules/@vue/compiler-dom": { 88 | "version": "3.2.45", 89 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", 90 | "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", 91 | "dependencies": { 92 | "@vue/compiler-core": "3.2.45", 93 | "@vue/shared": "3.2.45" 94 | } 95 | }, 96 | "node_modules/@vue/compiler-sfc": { 97 | "version": "3.2.45", 98 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", 99 | "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", 100 | "dependencies": { 101 | "@babel/parser": "^7.16.4", 102 | "@vue/compiler-core": "3.2.45", 103 | "@vue/compiler-dom": "3.2.45", 104 | "@vue/compiler-ssr": "3.2.45", 105 | "@vue/reactivity-transform": "3.2.45", 106 | "@vue/shared": "3.2.45", 107 | "estree-walker": "^2.0.2", 108 | "magic-string": "^0.25.7", 109 | "postcss": "^8.1.10", 110 | "source-map": "^0.6.1" 111 | } 112 | }, 113 | "node_modules/@vue/compiler-ssr": { 114 | "version": "3.2.45", 115 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", 116 | "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", 117 | "dependencies": { 118 | "@vue/compiler-dom": "3.2.45", 119 | "@vue/shared": "3.2.45" 120 | } 121 | }, 122 | "node_modules/@vue/reactivity": { 123 | "version": "3.2.45", 124 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", 125 | "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", 126 | "dependencies": { 127 | "@vue/shared": "3.2.45" 128 | } 129 | }, 130 | "node_modules/@vue/reactivity-transform": { 131 | "version": "3.2.45", 132 | "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", 133 | "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", 134 | "dependencies": { 135 | "@babel/parser": "^7.16.4", 136 | "@vue/compiler-core": "3.2.45", 137 | "@vue/shared": "3.2.45", 138 | "estree-walker": "^2.0.2", 139 | "magic-string": "^0.25.7" 140 | } 141 | }, 142 | "node_modules/@vue/runtime-core": { 143 | "version": "3.2.45", 144 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", 145 | "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", 146 | "dependencies": { 147 | "@vue/reactivity": "3.2.45", 148 | "@vue/shared": "3.2.45" 149 | } 150 | }, 151 | "node_modules/@vue/runtime-dom": { 152 | "version": "3.2.45", 153 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", 154 | "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", 155 | "dependencies": { 156 | "@vue/runtime-core": "3.2.45", 157 | "@vue/shared": "3.2.45", 158 | "csstype": "^2.6.8" 159 | } 160 | }, 161 | "node_modules/@vue/server-renderer": { 162 | "version": "3.2.45", 163 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", 164 | "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", 165 | "dependencies": { 166 | "@vue/compiler-ssr": "3.2.45", 167 | "@vue/shared": "3.2.45" 168 | }, 169 | "peerDependencies": { 170 | "vue": "3.2.45" 171 | } 172 | }, 173 | "node_modules/@vue/shared": { 174 | "version": "3.2.45", 175 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", 176 | "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" 177 | }, 178 | "node_modules/csstype": { 179 | "version": "2.6.21", 180 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", 181 | "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" 182 | }, 183 | "node_modules/esbuild": { 184 | "version": "0.15.18", 185 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", 186 | "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", 187 | "dev": true, 188 | "hasInstallScript": true, 189 | "bin": { 190 | "esbuild": "bin/esbuild" 191 | }, 192 | "engines": { 193 | "node": ">=12" 194 | }, 195 | "optionalDependencies": { 196 | "@esbuild/android-arm": "0.15.18", 197 | "@esbuild/linux-loong64": "0.15.18", 198 | "esbuild-android-64": "0.15.18", 199 | "esbuild-android-arm64": "0.15.18", 200 | "esbuild-darwin-64": "0.15.18", 201 | "esbuild-darwin-arm64": "0.15.18", 202 | "esbuild-freebsd-64": "0.15.18", 203 | "esbuild-freebsd-arm64": "0.15.18", 204 | "esbuild-linux-32": "0.15.18", 205 | "esbuild-linux-64": "0.15.18", 206 | "esbuild-linux-arm": "0.15.18", 207 | "esbuild-linux-arm64": "0.15.18", 208 | "esbuild-linux-mips64le": "0.15.18", 209 | "esbuild-linux-ppc64le": "0.15.18", 210 | "esbuild-linux-riscv64": "0.15.18", 211 | "esbuild-linux-s390x": "0.15.18", 212 | "esbuild-netbsd-64": "0.15.18", 213 | "esbuild-openbsd-64": "0.15.18", 214 | "esbuild-sunos-64": "0.15.18", 215 | "esbuild-windows-32": "0.15.18", 216 | "esbuild-windows-64": "0.15.18", 217 | "esbuild-windows-arm64": "0.15.18" 218 | } 219 | }, 220 | "node_modules/esbuild-android-64": { 221 | "version": "0.15.18", 222 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", 223 | "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", 224 | "cpu": [ 225 | "x64" 226 | ], 227 | "dev": true, 228 | "optional": true, 229 | "os": [ 230 | "android" 231 | ], 232 | "engines": { 233 | "node": ">=12" 234 | } 235 | }, 236 | "node_modules/esbuild-android-arm64": { 237 | "version": "0.15.18", 238 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", 239 | "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", 240 | "cpu": [ 241 | "arm64" 242 | ], 243 | "dev": true, 244 | "optional": true, 245 | "os": [ 246 | "android" 247 | ], 248 | "engines": { 249 | "node": ">=12" 250 | } 251 | }, 252 | "node_modules/esbuild-darwin-64": { 253 | "version": "0.15.18", 254 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", 255 | "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", 256 | "cpu": [ 257 | "x64" 258 | ], 259 | "dev": true, 260 | "optional": true, 261 | "os": [ 262 | "darwin" 263 | ], 264 | "engines": { 265 | "node": ">=12" 266 | } 267 | }, 268 | "node_modules/esbuild-darwin-arm64": { 269 | "version": "0.15.18", 270 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", 271 | "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", 272 | "cpu": [ 273 | "arm64" 274 | ], 275 | "dev": true, 276 | "optional": true, 277 | "os": [ 278 | "darwin" 279 | ], 280 | "engines": { 281 | "node": ">=12" 282 | } 283 | }, 284 | "node_modules/esbuild-freebsd-64": { 285 | "version": "0.15.18", 286 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", 287 | "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", 288 | "cpu": [ 289 | "x64" 290 | ], 291 | "dev": true, 292 | "optional": true, 293 | "os": [ 294 | "freebsd" 295 | ], 296 | "engines": { 297 | "node": ">=12" 298 | } 299 | }, 300 | "node_modules/esbuild-freebsd-arm64": { 301 | "version": "0.15.18", 302 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", 303 | "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", 304 | "cpu": [ 305 | "arm64" 306 | ], 307 | "dev": true, 308 | "optional": true, 309 | "os": [ 310 | "freebsd" 311 | ], 312 | "engines": { 313 | "node": ">=12" 314 | } 315 | }, 316 | "node_modules/esbuild-linux-32": { 317 | "version": "0.15.18", 318 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", 319 | "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", 320 | "cpu": [ 321 | "ia32" 322 | ], 323 | "dev": true, 324 | "optional": true, 325 | "os": [ 326 | "linux" 327 | ], 328 | "engines": { 329 | "node": ">=12" 330 | } 331 | }, 332 | "node_modules/esbuild-linux-64": { 333 | "version": "0.15.18", 334 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", 335 | "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", 336 | "cpu": [ 337 | "x64" 338 | ], 339 | "dev": true, 340 | "optional": true, 341 | "os": [ 342 | "linux" 343 | ], 344 | "engines": { 345 | "node": ">=12" 346 | } 347 | }, 348 | "node_modules/esbuild-linux-arm": { 349 | "version": "0.15.18", 350 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", 351 | "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", 352 | "cpu": [ 353 | "arm" 354 | ], 355 | "dev": true, 356 | "optional": true, 357 | "os": [ 358 | "linux" 359 | ], 360 | "engines": { 361 | "node": ">=12" 362 | } 363 | }, 364 | "node_modules/esbuild-linux-arm64": { 365 | "version": "0.15.18", 366 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", 367 | "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", 368 | "cpu": [ 369 | "arm64" 370 | ], 371 | "dev": true, 372 | "optional": true, 373 | "os": [ 374 | "linux" 375 | ], 376 | "engines": { 377 | "node": ">=12" 378 | } 379 | }, 380 | "node_modules/esbuild-linux-mips64le": { 381 | "version": "0.15.18", 382 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", 383 | "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", 384 | "cpu": [ 385 | "mips64el" 386 | ], 387 | "dev": true, 388 | "optional": true, 389 | "os": [ 390 | "linux" 391 | ], 392 | "engines": { 393 | "node": ">=12" 394 | } 395 | }, 396 | "node_modules/esbuild-linux-ppc64le": { 397 | "version": "0.15.18", 398 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", 399 | "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", 400 | "cpu": [ 401 | "ppc64" 402 | ], 403 | "dev": true, 404 | "optional": true, 405 | "os": [ 406 | "linux" 407 | ], 408 | "engines": { 409 | "node": ">=12" 410 | } 411 | }, 412 | "node_modules/esbuild-linux-riscv64": { 413 | "version": "0.15.18", 414 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", 415 | "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", 416 | "cpu": [ 417 | "riscv64" 418 | ], 419 | "dev": true, 420 | "optional": true, 421 | "os": [ 422 | "linux" 423 | ], 424 | "engines": { 425 | "node": ">=12" 426 | } 427 | }, 428 | "node_modules/esbuild-linux-s390x": { 429 | "version": "0.15.18", 430 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", 431 | "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", 432 | "cpu": [ 433 | "s390x" 434 | ], 435 | "dev": true, 436 | "optional": true, 437 | "os": [ 438 | "linux" 439 | ], 440 | "engines": { 441 | "node": ">=12" 442 | } 443 | }, 444 | "node_modules/esbuild-netbsd-64": { 445 | "version": "0.15.18", 446 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", 447 | "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", 448 | "cpu": [ 449 | "x64" 450 | ], 451 | "dev": true, 452 | "optional": true, 453 | "os": [ 454 | "netbsd" 455 | ], 456 | "engines": { 457 | "node": ">=12" 458 | } 459 | }, 460 | "node_modules/esbuild-openbsd-64": { 461 | "version": "0.15.18", 462 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", 463 | "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", 464 | "cpu": [ 465 | "x64" 466 | ], 467 | "dev": true, 468 | "optional": true, 469 | "os": [ 470 | "openbsd" 471 | ], 472 | "engines": { 473 | "node": ">=12" 474 | } 475 | }, 476 | "node_modules/esbuild-sunos-64": { 477 | "version": "0.15.18", 478 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", 479 | "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", 480 | "cpu": [ 481 | "x64" 482 | ], 483 | "dev": true, 484 | "optional": true, 485 | "os": [ 486 | "sunos" 487 | ], 488 | "engines": { 489 | "node": ">=12" 490 | } 491 | }, 492 | "node_modules/esbuild-windows-32": { 493 | "version": "0.15.18", 494 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", 495 | "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", 496 | "cpu": [ 497 | "ia32" 498 | ], 499 | "dev": true, 500 | "optional": true, 501 | "os": [ 502 | "win32" 503 | ], 504 | "engines": { 505 | "node": ">=12" 506 | } 507 | }, 508 | "node_modules/esbuild-windows-64": { 509 | "version": "0.15.18", 510 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", 511 | "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", 512 | "cpu": [ 513 | "x64" 514 | ], 515 | "dev": true, 516 | "optional": true, 517 | "os": [ 518 | "win32" 519 | ], 520 | "engines": { 521 | "node": ">=12" 522 | } 523 | }, 524 | "node_modules/esbuild-windows-arm64": { 525 | "version": "0.15.18", 526 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", 527 | "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", 528 | "cpu": [ 529 | "arm64" 530 | ], 531 | "dev": true, 532 | "optional": true, 533 | "os": [ 534 | "win32" 535 | ], 536 | "engines": { 537 | "node": ">=12" 538 | } 539 | }, 540 | "node_modules/estree-walker": { 541 | "version": "2.0.2", 542 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 543 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 544 | }, 545 | "node_modules/fsevents": { 546 | "version": "2.3.2", 547 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 548 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 549 | "dev": true, 550 | "hasInstallScript": true, 551 | "optional": true, 552 | "os": [ 553 | "darwin" 554 | ], 555 | "engines": { 556 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 557 | } 558 | }, 559 | "node_modules/function-bind": { 560 | "version": "1.1.1", 561 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 562 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 563 | "dev": true 564 | }, 565 | "node_modules/has": { 566 | "version": "1.0.3", 567 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 568 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 569 | "dev": true, 570 | "dependencies": { 571 | "function-bind": "^1.1.1" 572 | }, 573 | "engines": { 574 | "node": ">= 0.4.0" 575 | } 576 | }, 577 | "node_modules/is-core-module": { 578 | "version": "2.11.0", 579 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 580 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 581 | "dev": true, 582 | "dependencies": { 583 | "has": "^1.0.3" 584 | }, 585 | "funding": { 586 | "url": "https://github.com/sponsors/ljharb" 587 | } 588 | }, 589 | "node_modules/magic-string": { 590 | "version": "0.25.9", 591 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", 592 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", 593 | "dependencies": { 594 | "sourcemap-codec": "^1.4.8" 595 | } 596 | }, 597 | "node_modules/nanoid": { 598 | "version": "3.3.4", 599 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 600 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", 601 | "bin": { 602 | "nanoid": "bin/nanoid.cjs" 603 | }, 604 | "engines": { 605 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 606 | } 607 | }, 608 | "node_modules/path-parse": { 609 | "version": "1.0.7", 610 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 611 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 612 | "dev": true 613 | }, 614 | "node_modules/picocolors": { 615 | "version": "1.0.0", 616 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 617 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 618 | }, 619 | "node_modules/postcss": { 620 | "version": "8.4.19", 621 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", 622 | "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", 623 | "funding": [ 624 | { 625 | "type": "opencollective", 626 | "url": "https://opencollective.com/postcss/" 627 | }, 628 | { 629 | "type": "tidelift", 630 | "url": "https://tidelift.com/funding/github/npm/postcss" 631 | } 632 | ], 633 | "dependencies": { 634 | "nanoid": "^3.3.4", 635 | "picocolors": "^1.0.0", 636 | "source-map-js": "^1.0.2" 637 | }, 638 | "engines": { 639 | "node": "^10 || ^12 || >=14" 640 | } 641 | }, 642 | "node_modules/resolve": { 643 | "version": "1.22.1", 644 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 645 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 646 | "dev": true, 647 | "dependencies": { 648 | "is-core-module": "^2.9.0", 649 | "path-parse": "^1.0.7", 650 | "supports-preserve-symlinks-flag": "^1.0.0" 651 | }, 652 | "bin": { 653 | "resolve": "bin/resolve" 654 | }, 655 | "funding": { 656 | "url": "https://github.com/sponsors/ljharb" 657 | } 658 | }, 659 | "node_modules/rollup": { 660 | "version": "2.79.1", 661 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", 662 | "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", 663 | "dev": true, 664 | "bin": { 665 | "rollup": "dist/bin/rollup" 666 | }, 667 | "engines": { 668 | "node": ">=10.0.0" 669 | }, 670 | "optionalDependencies": { 671 | "fsevents": "~2.3.2" 672 | } 673 | }, 674 | "node_modules/source-map": { 675 | "version": "0.6.1", 676 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 677 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 678 | "engines": { 679 | "node": ">=0.10.0" 680 | } 681 | }, 682 | "node_modules/source-map-js": { 683 | "version": "1.0.2", 684 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 685 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 686 | "engines": { 687 | "node": ">=0.10.0" 688 | } 689 | }, 690 | "node_modules/sourcemap-codec": { 691 | "version": "1.4.8", 692 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 693 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", 694 | "deprecated": "Please use @jridgewell/sourcemap-codec instead" 695 | }, 696 | "node_modules/supports-preserve-symlinks-flag": { 697 | "version": "1.0.0", 698 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 699 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 700 | "dev": true, 701 | "engines": { 702 | "node": ">= 0.4" 703 | }, 704 | "funding": { 705 | "url": "https://github.com/sponsors/ljharb" 706 | } 707 | }, 708 | "node_modules/vite": { 709 | "version": "3.2.5", 710 | "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", 711 | "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", 712 | "dev": true, 713 | "dependencies": { 714 | "esbuild": "^0.15.9", 715 | "postcss": "^8.4.18", 716 | "resolve": "^1.22.1", 717 | "rollup": "^2.79.1" 718 | }, 719 | "bin": { 720 | "vite": "bin/vite.js" 721 | }, 722 | "engines": { 723 | "node": "^14.18.0 || >=16.0.0" 724 | }, 725 | "optionalDependencies": { 726 | "fsevents": "~2.3.2" 727 | }, 728 | "peerDependencies": { 729 | "@types/node": ">= 14", 730 | "less": "*", 731 | "sass": "*", 732 | "stylus": "*", 733 | "sugarss": "*", 734 | "terser": "^5.4.0" 735 | }, 736 | "peerDependenciesMeta": { 737 | "@types/node": { 738 | "optional": true 739 | }, 740 | "less": { 741 | "optional": true 742 | }, 743 | "sass": { 744 | "optional": true 745 | }, 746 | "stylus": { 747 | "optional": true 748 | }, 749 | "sugarss": { 750 | "optional": true 751 | }, 752 | "terser": { 753 | "optional": true 754 | } 755 | } 756 | }, 757 | "node_modules/vue": { 758 | "version": "3.2.45", 759 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", 760 | "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", 761 | "dependencies": { 762 | "@vue/compiler-dom": "3.2.45", 763 | "@vue/compiler-sfc": "3.2.45", 764 | "@vue/runtime-dom": "3.2.45", 765 | "@vue/server-renderer": "3.2.45", 766 | "@vue/shared": "3.2.45" 767 | } 768 | }, 769 | "node_modules/vue3-canvas-video-player": { 770 | "version": "1.1.2", 771 | "resolved": "https://registry.npmjs.org/vue3-canvas-video-player/-/vue3-canvas-video-player-1.1.2.tgz", 772 | "integrity": "sha512-LCiJ/VIlmbmMRtbUBKnrRMurF5g6Bn0V5hHRuOJg8N8zo6BBnsACLwQrwO4A0TfW+n/SfuoSpjGIXw5IvdHY0A==", 773 | "dependencies": { 774 | "vue": "^3.2.41", 775 | "vue3-canvas-video-player": "^1.0.3" 776 | }, 777 | "engines": { 778 | "node": ">=16.0.0", 779 | "npm": "~8.1.2" 780 | } 781 | } 782 | }, 783 | "dependencies": { 784 | "@babel/parser": { 785 | "version": "7.20.5", 786 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", 787 | "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" 788 | }, 789 | "@esbuild/android-arm": { 790 | "version": "0.15.18", 791 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", 792 | "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", 793 | "dev": true, 794 | "optional": true 795 | }, 796 | "@esbuild/linux-loong64": { 797 | "version": "0.15.18", 798 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", 799 | "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", 800 | "dev": true, 801 | "optional": true 802 | }, 803 | "@vitejs/plugin-vue": { 804 | "version": "3.2.0", 805 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz", 806 | "integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==", 807 | "dev": true, 808 | "requires": {} 809 | }, 810 | "@vue/compiler-core": { 811 | "version": "3.2.45", 812 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", 813 | "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", 814 | "requires": { 815 | "@babel/parser": "^7.16.4", 816 | "@vue/shared": "3.2.45", 817 | "estree-walker": "^2.0.2", 818 | "source-map": "^0.6.1" 819 | } 820 | }, 821 | "@vue/compiler-dom": { 822 | "version": "3.2.45", 823 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", 824 | "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", 825 | "requires": { 826 | "@vue/compiler-core": "3.2.45", 827 | "@vue/shared": "3.2.45" 828 | } 829 | }, 830 | "@vue/compiler-sfc": { 831 | "version": "3.2.45", 832 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz", 833 | "integrity": "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==", 834 | "requires": { 835 | "@babel/parser": "^7.16.4", 836 | "@vue/compiler-core": "3.2.45", 837 | "@vue/compiler-dom": "3.2.45", 838 | "@vue/compiler-ssr": "3.2.45", 839 | "@vue/reactivity-transform": "3.2.45", 840 | "@vue/shared": "3.2.45", 841 | "estree-walker": "^2.0.2", 842 | "magic-string": "^0.25.7", 843 | "postcss": "^8.1.10", 844 | "source-map": "^0.6.1" 845 | } 846 | }, 847 | "@vue/compiler-ssr": { 848 | "version": "3.2.45", 849 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz", 850 | "integrity": "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==", 851 | "requires": { 852 | "@vue/compiler-dom": "3.2.45", 853 | "@vue/shared": "3.2.45" 854 | } 855 | }, 856 | "@vue/reactivity": { 857 | "version": "3.2.45", 858 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz", 859 | "integrity": "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==", 860 | "requires": { 861 | "@vue/shared": "3.2.45" 862 | } 863 | }, 864 | "@vue/reactivity-transform": { 865 | "version": "3.2.45", 866 | "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz", 867 | "integrity": "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==", 868 | "requires": { 869 | "@babel/parser": "^7.16.4", 870 | "@vue/compiler-core": "3.2.45", 871 | "@vue/shared": "3.2.45", 872 | "estree-walker": "^2.0.2", 873 | "magic-string": "^0.25.7" 874 | } 875 | }, 876 | "@vue/runtime-core": { 877 | "version": "3.2.45", 878 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz", 879 | "integrity": "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==", 880 | "requires": { 881 | "@vue/reactivity": "3.2.45", 882 | "@vue/shared": "3.2.45" 883 | } 884 | }, 885 | "@vue/runtime-dom": { 886 | "version": "3.2.45", 887 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz", 888 | "integrity": "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==", 889 | "requires": { 890 | "@vue/runtime-core": "3.2.45", 891 | "@vue/shared": "3.2.45", 892 | "csstype": "^2.6.8" 893 | } 894 | }, 895 | "@vue/server-renderer": { 896 | "version": "3.2.45", 897 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz", 898 | "integrity": "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==", 899 | "requires": { 900 | "@vue/compiler-ssr": "3.2.45", 901 | "@vue/shared": "3.2.45" 902 | } 903 | }, 904 | "@vue/shared": { 905 | "version": "3.2.45", 906 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", 907 | "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==" 908 | }, 909 | "csstype": { 910 | "version": "2.6.21", 911 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", 912 | "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" 913 | }, 914 | "esbuild": { 915 | "version": "0.15.18", 916 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", 917 | "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", 918 | "dev": true, 919 | "requires": { 920 | "@esbuild/android-arm": "0.15.18", 921 | "@esbuild/linux-loong64": "0.15.18", 922 | "esbuild-android-64": "0.15.18", 923 | "esbuild-android-arm64": "0.15.18", 924 | "esbuild-darwin-64": "0.15.18", 925 | "esbuild-darwin-arm64": "0.15.18", 926 | "esbuild-freebsd-64": "0.15.18", 927 | "esbuild-freebsd-arm64": "0.15.18", 928 | "esbuild-linux-32": "0.15.18", 929 | "esbuild-linux-64": "0.15.18", 930 | "esbuild-linux-arm": "0.15.18", 931 | "esbuild-linux-arm64": "0.15.18", 932 | "esbuild-linux-mips64le": "0.15.18", 933 | "esbuild-linux-ppc64le": "0.15.18", 934 | "esbuild-linux-riscv64": "0.15.18", 935 | "esbuild-linux-s390x": "0.15.18", 936 | "esbuild-netbsd-64": "0.15.18", 937 | "esbuild-openbsd-64": "0.15.18", 938 | "esbuild-sunos-64": "0.15.18", 939 | "esbuild-windows-32": "0.15.18", 940 | "esbuild-windows-64": "0.15.18", 941 | "esbuild-windows-arm64": "0.15.18" 942 | } 943 | }, 944 | "esbuild-android-64": { 945 | "version": "0.15.18", 946 | "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", 947 | "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", 948 | "dev": true, 949 | "optional": true 950 | }, 951 | "esbuild-android-arm64": { 952 | "version": "0.15.18", 953 | "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", 954 | "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", 955 | "dev": true, 956 | "optional": true 957 | }, 958 | "esbuild-darwin-64": { 959 | "version": "0.15.18", 960 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", 961 | "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", 962 | "dev": true, 963 | "optional": true 964 | }, 965 | "esbuild-darwin-arm64": { 966 | "version": "0.15.18", 967 | "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", 968 | "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", 969 | "dev": true, 970 | "optional": true 971 | }, 972 | "esbuild-freebsd-64": { 973 | "version": "0.15.18", 974 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", 975 | "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", 976 | "dev": true, 977 | "optional": true 978 | }, 979 | "esbuild-freebsd-arm64": { 980 | "version": "0.15.18", 981 | "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", 982 | "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", 983 | "dev": true, 984 | "optional": true 985 | }, 986 | "esbuild-linux-32": { 987 | "version": "0.15.18", 988 | "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", 989 | "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", 990 | "dev": true, 991 | "optional": true 992 | }, 993 | "esbuild-linux-64": { 994 | "version": "0.15.18", 995 | "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", 996 | "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", 997 | "dev": true, 998 | "optional": true 999 | }, 1000 | "esbuild-linux-arm": { 1001 | "version": "0.15.18", 1002 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", 1003 | "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", 1004 | "dev": true, 1005 | "optional": true 1006 | }, 1007 | "esbuild-linux-arm64": { 1008 | "version": "0.15.18", 1009 | "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", 1010 | "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", 1011 | "dev": true, 1012 | "optional": true 1013 | }, 1014 | "esbuild-linux-mips64le": { 1015 | "version": "0.15.18", 1016 | "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", 1017 | "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", 1018 | "dev": true, 1019 | "optional": true 1020 | }, 1021 | "esbuild-linux-ppc64le": { 1022 | "version": "0.15.18", 1023 | "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", 1024 | "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", 1025 | "dev": true, 1026 | "optional": true 1027 | }, 1028 | "esbuild-linux-riscv64": { 1029 | "version": "0.15.18", 1030 | "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", 1031 | "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", 1032 | "dev": true, 1033 | "optional": true 1034 | }, 1035 | "esbuild-linux-s390x": { 1036 | "version": "0.15.18", 1037 | "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", 1038 | "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", 1039 | "dev": true, 1040 | "optional": true 1041 | }, 1042 | "esbuild-netbsd-64": { 1043 | "version": "0.15.18", 1044 | "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", 1045 | "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", 1046 | "dev": true, 1047 | "optional": true 1048 | }, 1049 | "esbuild-openbsd-64": { 1050 | "version": "0.15.18", 1051 | "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", 1052 | "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", 1053 | "dev": true, 1054 | "optional": true 1055 | }, 1056 | "esbuild-sunos-64": { 1057 | "version": "0.15.18", 1058 | "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", 1059 | "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", 1060 | "dev": true, 1061 | "optional": true 1062 | }, 1063 | "esbuild-windows-32": { 1064 | "version": "0.15.18", 1065 | "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", 1066 | "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", 1067 | "dev": true, 1068 | "optional": true 1069 | }, 1070 | "esbuild-windows-64": { 1071 | "version": "0.15.18", 1072 | "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", 1073 | "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", 1074 | "dev": true, 1075 | "optional": true 1076 | }, 1077 | "esbuild-windows-arm64": { 1078 | "version": "0.15.18", 1079 | "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", 1080 | "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", 1081 | "dev": true, 1082 | "optional": true 1083 | }, 1084 | "estree-walker": { 1085 | "version": "2.0.2", 1086 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1087 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 1088 | }, 1089 | "fsevents": { 1090 | "version": "2.3.2", 1091 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 1092 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 1093 | "dev": true, 1094 | "optional": true 1095 | }, 1096 | "function-bind": { 1097 | "version": "1.1.1", 1098 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 1099 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 1100 | "dev": true 1101 | }, 1102 | "has": { 1103 | "version": "1.0.3", 1104 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 1105 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 1106 | "dev": true, 1107 | "requires": { 1108 | "function-bind": "^1.1.1" 1109 | } 1110 | }, 1111 | "is-core-module": { 1112 | "version": "2.11.0", 1113 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", 1114 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 1115 | "dev": true, 1116 | "requires": { 1117 | "has": "^1.0.3" 1118 | } 1119 | }, 1120 | "magic-string": { 1121 | "version": "0.25.9", 1122 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", 1123 | "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", 1124 | "requires": { 1125 | "sourcemap-codec": "^1.4.8" 1126 | } 1127 | }, 1128 | "nanoid": { 1129 | "version": "3.3.4", 1130 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", 1131 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" 1132 | }, 1133 | "path-parse": { 1134 | "version": "1.0.7", 1135 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1136 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1137 | "dev": true 1138 | }, 1139 | "picocolors": { 1140 | "version": "1.0.0", 1141 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 1142 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 1143 | }, 1144 | "postcss": { 1145 | "version": "8.4.19", 1146 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", 1147 | "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", 1148 | "requires": { 1149 | "nanoid": "^3.3.4", 1150 | "picocolors": "^1.0.0", 1151 | "source-map-js": "^1.0.2" 1152 | } 1153 | }, 1154 | "resolve": { 1155 | "version": "1.22.1", 1156 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", 1157 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 1158 | "dev": true, 1159 | "requires": { 1160 | "is-core-module": "^2.9.0", 1161 | "path-parse": "^1.0.7", 1162 | "supports-preserve-symlinks-flag": "^1.0.0" 1163 | } 1164 | }, 1165 | "rollup": { 1166 | "version": "2.79.1", 1167 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", 1168 | "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", 1169 | "dev": true, 1170 | "requires": { 1171 | "fsevents": "~2.3.2" 1172 | } 1173 | }, 1174 | "source-map": { 1175 | "version": "0.6.1", 1176 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1177 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 1178 | }, 1179 | "source-map-js": { 1180 | "version": "1.0.2", 1181 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 1182 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" 1183 | }, 1184 | "sourcemap-codec": { 1185 | "version": "1.4.8", 1186 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", 1187 | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" 1188 | }, 1189 | "supports-preserve-symlinks-flag": { 1190 | "version": "1.0.0", 1191 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1192 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1193 | "dev": true 1194 | }, 1195 | "vite": { 1196 | "version": "3.2.5", 1197 | "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz", 1198 | "integrity": "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ==", 1199 | "dev": true, 1200 | "requires": { 1201 | "esbuild": "^0.15.9", 1202 | "fsevents": "~2.3.2", 1203 | "postcss": "^8.4.18", 1204 | "resolve": "^1.22.1", 1205 | "rollup": "^2.79.1" 1206 | } 1207 | }, 1208 | "vue": { 1209 | "version": "3.2.45", 1210 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz", 1211 | "integrity": "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==", 1212 | "requires": { 1213 | "@vue/compiler-dom": "3.2.45", 1214 | "@vue/compiler-sfc": "3.2.45", 1215 | "@vue/runtime-dom": "3.2.45", 1216 | "@vue/server-renderer": "3.2.45", 1217 | "@vue/shared": "3.2.45" 1218 | } 1219 | }, 1220 | "vue3-canvas-video-player": { 1221 | "version": "1.1.2", 1222 | "resolved": "https://registry.npmjs.org/vue3-canvas-video-player/-/vue3-canvas-video-player-1.1.2.tgz", 1223 | "integrity": "sha512-LCiJ/VIlmbmMRtbUBKnrRMurF5g6Bn0V5hHRuOJg8N8zo6BBnsACLwQrwO4A0TfW+n/SfuoSpjGIXw5IvdHY0A==", 1224 | "requires": { 1225 | "vue": "^3.2.41", 1226 | "vue3-canvas-video-player": "^1.0.3" 1227 | } 1228 | } 1229 | } 1230 | } 1231 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-canvas-video-player", 3 | "version": "1.2.3", 4 | "description": "Video player that operates on Vue3 and uses canvas", 5 | "author": "GronkOut ", 6 | "keywords": [ 7 | "vue3", 8 | "canvas", 9 | "video", 10 | "player", 11 | "range", 12 | "fps", 13 | "boundingbox" 14 | ], 15 | "homepage": "https://github.com/GronkOut/vue3-canvas-video-player", 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/GronkOut/vue3-canvas-video-player.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/GronkOut/vue3-canvas-video-player/issues", 22 | "email": "gronkout@gmail.com" 23 | }, 24 | "directories": { 25 | "example": "examples", 26 | "lib": "lib" 27 | }, 28 | "files": [ 29 | "dist" 30 | ], 31 | "main": "./dist/vue3-canvas-video-player.umd.cjs", 32 | "module": "./dist/vue3-canvas-video-player.js", 33 | "type": "module", 34 | "exports": { 35 | ".": { 36 | "import": "./dist/vue3-canvas-video-player.js", 37 | "require": "./dist/vue3-canvas-video-player.umd.js" 38 | }, 39 | "./dist/style.css": "./dist/style.css" 40 | }, 41 | "scripts": { 42 | "start": "vite", 43 | "build": "vite build" 44 | }, 45 | "dependencies": { 46 | "vue": "^3.2.41", 47 | "vue3-canvas-video-player": "^1.0.3" 48 | }, 49 | "devDependencies": { 50 | "@vitejs/plugin-vue": "^3.2.0", 51 | "vite": "^3.2.3" 52 | }, 53 | "license": "MIT" 54 | } 55 | -------------------------------------------------------------------------------- /public/bbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GronkOut/vue3-canvas-video-player/5ab7732cc4c5cca052b3676f7a86a5245adcecf4/public/bbox.gif -------------------------------------------------------------------------------- /public/darkMode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GronkOut/vue3-canvas-video-player/5ab7732cc4c5cca052b3676f7a86a5245adcecf4/public/darkMode.gif -------------------------------------------------------------------------------- /public/fps.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GronkOut/vue3-canvas-video-player/5ab7732cc4c5cca052b3676f7a86a5245adcecf4/public/fps.gif -------------------------------------------------------------------------------- /public/messageTime.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GronkOut/vue3-canvas-video-player/5ab7732cc4c5cca052b3676f7a86a5245adcecf4/public/messageTime.gif -------------------------------------------------------------------------------- /public/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GronkOut/vue3-canvas-video-player/5ab7732cc4c5cca052b3676f7a86a5245adcecf4/public/preview.gif -------------------------------------------------------------------------------- /public/range.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GronkOut/vue3-canvas-video-player/5ab7732cc4c5cca052b3676f7a86a5245adcecf4/public/range.gif -------------------------------------------------------------------------------- /public/type.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GronkOut/vue3-canvas-video-player/5ab7732cc4c5cca052b3676f7a86a5245adcecf4/public/type.gif -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 153 | 154 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | 4 | createApp(App).mount('#app'); 5 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | a { 28 | font-weight: 500; 29 | color: #646cff; 30 | text-decoration: inherit; 31 | } 32 | a:hover { 33 | color: #535bf2; 34 | } 35 | 36 | body { 37 | margin: 0; 38 | display: flex; 39 | place-items: center; 40 | min-width: 320px; 41 | min-height: 100vh; 42 | } 43 | 44 | h1 { 45 | font-size: 3.2em; 46 | line-height: 1.1; 47 | } 48 | 49 | button { 50 | border-radius: 8px; 51 | border: 1px solid transparent; 52 | padding: 0.6em 1.2em; 53 | font-size: 1em; 54 | font-weight: 500; 55 | font-family: inherit; 56 | background-color: #1a1a1a; 57 | cursor: pointer; 58 | transition: border-color 0.25s; 59 | } 60 | button:hover { 61 | border-color: #646cff; 62 | } 63 | button:focus, 64 | button:focus-visible { 65 | outline: 4px auto -webkit-focus-ring-color; 66 | } 67 | 68 | .card { 69 | padding: 2em; 70 | } 71 | 72 | #app { 73 | max-width: 1280px; 74 | margin: 0 auto; 75 | padding: 2rem; 76 | text-align: center; 77 | } 78 | 79 | @media (prefers-color-scheme: light) { 80 | :root { 81 | color: #213547; 82 | background-color: #ffffff; 83 | } 84 | a:hover { 85 | color: #747bff; 86 | } 87 | button { 88 | background-color: #f9f9f9; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | publicDir: false, 8 | build: { 9 | lib: { 10 | entry: resolve(__dirname, 'lib/main.js'), 11 | name: 'Vue3CanvasVideoPlayer', 12 | fileName: 'vue3-canvas-video-player', 13 | }, 14 | rollupOptions: { 15 | external: ['vue'], 16 | output: { 17 | globals: { 18 | vue: 'Vue', 19 | }, 20 | }, 21 | }, 22 | }, 23 | }); 24 | --------------------------------------------------------------------------------