@give-tech/ec-player-vue 0.0.1-beta.31 → 0.0.1-beta.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Vue Composables 导出
3
+ */
4
+ export { usePlayer, type UsePlayerReturn } from './usePlayer';
5
+ export { useControls, type UseControlsParams, type UseControlsReturn } from './useControls';
6
+ export { useEnvInfo, type UseEnvInfoReturn } from './useEnvInfo';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/composables/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAC3F,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 播放器控制条逻辑
3
+ */
4
+ import { type Ref } from 'vue';
5
+ /**
6
+ * 控制条逻辑参数
7
+ */
8
+ export interface UseControlsParams {
9
+ /** 是否正在播放 */
10
+ isPlaying: Ref<boolean>;
11
+ /** 是否已加载 */
12
+ isLoaded: Ref<boolean>;
13
+ /** 当前时间(毫秒) */
14
+ currentTime: Ref<number>;
15
+ /** 总时长(毫秒) */
16
+ duration: Ref<number>;
17
+ /** 已解析的分片索引 */
18
+ segmentIndex: Ref<number>;
19
+ /** 已下载的分片总数 */
20
+ fetchedSegmentCount: Ref<number>;
21
+ /** 总分片数 */
22
+ totalSegments: Ref<number>;
23
+ /** 是否为直播流 */
24
+ isLive: Ref<boolean>;
25
+ /** 跳转方法 */
26
+ seek: (time: number) => Promise<void>;
27
+ }
28
+ /**
29
+ * 控制条逻辑返回值
30
+ */
31
+ export interface UseControlsReturn {
32
+ /** 是否显示控制条 */
33
+ showControls: Ref<boolean>;
34
+ /** 进度条 hover 时间 */
35
+ hoverTime: Ref<number | null>;
36
+ /** 进度条 hover 位置 */
37
+ hoverPosition: Ref<number>;
38
+ /** 播放进度百分比 */
39
+ playProgress: Ref<number>;
40
+ /** 缓冲进度百分比 */
41
+ bufferProgress: Ref<number>;
42
+ /** 显示控制条 */
43
+ showControlsBar: () => void;
44
+ /** 隐藏控制条 */
45
+ hideControlsBar: () => void;
46
+ /** 处理进度条点击 */
47
+ handleProgressClick: (event: MouseEvent) => Promise<void>;
48
+ /** 处理进度条悬停 */
49
+ handleProgressHover: (event: MouseEvent) => void;
50
+ /** 格式化时间 */
51
+ formatTime: (ms: number) => string;
52
+ }
53
+ /**
54
+ * 控制条逻辑 Hook
55
+ */
56
+ export declare function useControls(params: UseControlsParams): UseControlsReturn;
57
+ //# sourceMappingURL=useControls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useControls.d.ts","sourceRoot":"","sources":["../../src/composables/useControls.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAiB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AAE7C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,aAAa;IACb,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,YAAY;IACZ,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACtB,eAAe;IACf,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,cAAc;IACd,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACrB,eAAe;IACf,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,eAAe;IACf,mBAAmB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAChC,WAAW;IACX,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC1B,aAAa;IACb,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACpB,WAAW;IACX,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,cAAc;IACd,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAC1B,mBAAmB;IACnB,SAAS,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAC7B,mBAAmB;IACnB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC1B,cAAc;IACd,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACzB,cAAc;IACd,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,YAAY;IACZ,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,YAAY;IACZ,eAAe,EAAE,MAAM,IAAI,CAAA;IAC3B,cAAc;IACd,mBAAmB,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,cAAc;IACd,mBAAmB,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;IAChD,YAAY;IACZ,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;CACnC;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,CAqGxE"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * 环境信息逻辑
3
+ */
4
+ import { type Ref } from 'vue';
5
+ import { type EnvInfo } from '@give-tech/ec-player';
6
+ /**
7
+ * 环境信息逻辑返回值
8
+ */
9
+ export interface UseEnvInfoReturn {
10
+ /** 环境信息 */
11
+ envInfo: Ref<EnvInfo | null>;
12
+ /** 是否显示环境面板 */
13
+ showEnvPanel: Ref<boolean>;
14
+ /** 环境类型文本 */
15
+ envTypeText: Ref<string>;
16
+ /** 切换环境面板显示 */
17
+ toggleEnvPanel: () => void;
18
+ /** 检测环境 */
19
+ detectEnv: () => void;
20
+ }
21
+ /**
22
+ * 环境信息逻辑 Hook
23
+ */
24
+ export declare function useEnvInfo(initialShowPanel?: boolean): UseEnvInfoReturn;
25
+ //# sourceMappingURL=useEnvInfo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEnvInfo.d.ts","sourceRoot":"","sources":["../../src/composables/useEnvInfo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA4B,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AACxD,OAAO,EAAe,KAAK,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEhE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW;IACX,OAAO,EAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IAC5B,eAAe;IACf,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAC1B,aAAa;IACb,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACxB,eAAe;IACf,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,WAAW;IACX,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,gBAAgB,GAAE,OAAe,GAChC,gBAAgB,CAyClB"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * 播放器核心逻辑
3
+ */
4
+ import { type Ref } from 'vue';
5
+ import { EcPlayerCore, type EcPlayerState } from '@give-tech/ec-player';
6
+ import type { PlayerOptions } from '../types';
7
+ /**
8
+ * 播放器逻辑返回值
9
+ */
10
+ export interface UsePlayerReturn {
11
+ /** Canvas 元素引用 */
12
+ canvasRef: Ref<HTMLCanvasElement | null>;
13
+ /** 播放器实例 */
14
+ player: Ref<EcPlayerCore | null>;
15
+ /** 响应式状态 */
16
+ state: EcPlayerState;
17
+ /** 是否正在加载 */
18
+ isLoading: Ref<boolean>;
19
+ /** 检测到的格式 */
20
+ detectedFormat: Ref<string>;
21
+ /** 是否为直播流 */
22
+ isLive: Ref<boolean>;
23
+ /** 播放 */
24
+ play: (options: {
25
+ url: string;
26
+ isLive?: boolean;
27
+ }) => Promise<void>;
28
+ /** 设置配置 */
29
+ setOptions: (options: PlayerOptions) => void;
30
+ /** 设置日志级别 */
31
+ setLogLevel: (level: number) => void;
32
+ /** 暂停 */
33
+ pause: () => void;
34
+ /** 恢复播放 */
35
+ resume: () => Promise<void>;
36
+ /** 跳转 */
37
+ seek: (time: number) => Promise<void>;
38
+ /** 获取播放器实例 */
39
+ getPlayer: () => EcPlayerCore | null;
40
+ /** 获取状态 */
41
+ getState: () => EcPlayerState;
42
+ /** 获取当前时间 */
43
+ getCurrentTime: () => number;
44
+ /** 获取总时长 */
45
+ getDuration: () => number;
46
+ /** 销毁播放器 */
47
+ destroy: () => void;
48
+ }
49
+ /**
50
+ * 播放器逻辑 Hook
51
+ */
52
+ export declare function usePlayer(emit: {
53
+ (e: 'play'): void;
54
+ (e: 'pause'): void;
55
+ }): UsePlayerReturn;
56
+ //# sourceMappingURL=usePlayer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePlayer.d.ts","sourceRoot":"","sources":["../../src/composables/usePlayer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA0C,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AACtE,OAAO,EACL,YAAY,EAIZ,KAAK,aAAa,EAEnB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,UAAU,CAAA;AAG5D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kBAAkB;IAClB,SAAS,EAAE,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;IACxC,YAAY;IACZ,MAAM,EAAE,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;IAChC,YAAY;IACZ,KAAK,EAAE,aAAa,CAAA;IACpB,aAAa;IACb,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,aAAa;IACb,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,aAAa;IACb,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACpB,SAAS;IACT,IAAI,EAAE,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACnE,WAAW;IACX,UAAU,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,aAAa;IACb,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,SAAS;IACT,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,WAAW;IACX,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,SAAS;IACT,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,cAAc;IACd,SAAS,EAAE,MAAM,YAAY,GAAG,IAAI,CAAA;IACpC,WAAW;IACX,QAAQ,EAAE,MAAM,aAAa,CAAA;IAC7B,aAAa;IACb,cAAc,EAAE,MAAM,MAAM,CAAA;IAC5B,YAAY;IACZ,WAAW,EAAE,MAAM,MAAM,CAAA;IACzB,YAAY;IACZ,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAkBD;;GAEG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE;IACJ,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACnB,GACA,eAAe,CA2PjB"}
@@ -1,21 +1,21 @@
1
1
 
2
- .gt-player-container[data-v-00879d38] {
2
+ .gt-player-container[data-v-957d0fbe] {
3
3
  width: 100%;
4
4
  height: 100%;
5
5
  }
6
- .gt-video-wrapper[data-v-00879d38] {
6
+ .gt-video-wrapper[data-v-957d0fbe] {
7
7
  position: relative;
8
8
  width: 100%;
9
9
  height: 100%;
10
10
  background: #000;
11
11
  overflow: hidden;
12
12
  }
13
- .gt-player-canvas[data-v-00879d38] {
13
+ .gt-player-canvas[data-v-957d0fbe] {
14
14
  width: 100%;
15
15
  height: 100%;
16
16
  object-fit: contain;
17
17
  }
18
- .gt-placeholder[data-v-00879d38] {
18
+ .gt-placeholder[data-v-957d0fbe] {
19
19
  position: absolute;
20
20
  top: 0;
21
21
  left: 0;
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  /* 加载中动画 */
31
- .gt-loading-spinner[data-v-00879d38] {
31
+ .gt-loading-spinner[data-v-957d0fbe] {
32
32
  position: absolute;
33
33
  top: 0;
34
34
  left: 0;
@@ -40,22 +40,24 @@
40
40
  background: rgba(0, 0, 0, 0.5);
41
41
  z-index: 10;
42
42
  }
43
- .gt-spinner[data-v-00879d38] {
44
- width: 8vmin;
45
- height: 8vmin;
46
- border: 0.5vmin solid rgba(255, 255, 255, 0.3);
43
+ .gt-spinner[data-v-957d0fbe] {
44
+ width: 10%;
45
+ max-width: 48px;
46
+ min-width: 20px;
47
+ aspect-ratio: 1/1;
48
+ border: 0.3em solid rgba(255, 255, 255, 0.3);
47
49
  border-top-color: #fff;
48
50
  border-radius: 50%;
49
- animation: gt-spin-00879d38 1s linear infinite;
51
+ animation: gt-spin-957d0fbe 1s linear infinite;
50
52
  }
51
- @keyframes gt-spin-00879d38 {
53
+ @keyframes gt-spin-957d0fbe {
52
54
  to {
53
55
  transform: rotate(360deg);
54
56
  }
55
57
  }
56
58
 
57
59
  /* 视频控制条 */
58
- .gt-video-controls[data-v-00879d38] {
60
+ .gt-video-controls[data-v-957d0fbe] {
59
61
  position: absolute;
60
62
  bottom: 0;
61
63
  left: 0;
@@ -68,13 +70,13 @@ to {
68
70
  align-items: center;
69
71
  gap: 12px;
70
72
  }
71
- .gt-flex-spacer[data-v-00879d38] {
73
+ .gt-flex-spacer[data-v-957d0fbe] {
72
74
  flex: 1;
73
75
  }
74
- .gt-video-controls.visible[data-v-00879d38] {
76
+ .gt-video-controls.visible[data-v-957d0fbe] {
75
77
  opacity: 1;
76
78
  }
77
- .gt-control-btn[data-v-00879d38] {
79
+ .gt-control-btn[data-v-957d0fbe] {
78
80
  width: 28px;
79
81
  height: 28px;
80
82
  border: none;
@@ -88,14 +90,14 @@ to {
88
90
  transition: all 0.2s ease;
89
91
  flex-shrink: 0;
90
92
  }
91
- .gt-control-btn[data-v-00879d38]:hover {
93
+ .gt-control-btn[data-v-957d0fbe]:hover {
92
94
  background: rgba(255, 255, 255, 0.2);
93
95
  }
94
- .gt-control-btn svg[data-v-00879d38] {
96
+ .gt-control-btn svg[data-v-957d0fbe] {
95
97
  width: 14px;
96
98
  height: 14px;
97
99
  }
98
- .gt-progress-container[data-v-00879d38] {
100
+ .gt-progress-container[data-v-957d0fbe] {
99
101
  flex: 1;
100
102
  position: relative;
101
103
  cursor: pointer;
@@ -103,7 +105,7 @@ to {
103
105
  display: flex;
104
106
  align-items: center;
105
107
  }
106
- .gt-progress-bar[data-v-00879d38] {
108
+ .gt-progress-bar[data-v-957d0fbe] {
107
109
  width: 100%;
108
110
  height: 4px;
109
111
  background: rgba(255, 255, 255, 0.2);
@@ -112,10 +114,10 @@ to {
112
114
  overflow: hidden;
113
115
  transition: height 0.2s ease;
114
116
  }
115
- .gt-progress-container:hover .gt-progress-bar[data-v-00879d38] {
117
+ .gt-progress-container:hover .gt-progress-bar[data-v-957d0fbe] {
116
118
  height: 6px;
117
119
  }
118
- .gt-progress-buffered[data-v-00879d38] {
120
+ .gt-progress-buffered[data-v-957d0fbe] {
119
121
  position: absolute;
120
122
  top: 0;
121
123
  left: 0;
@@ -124,7 +126,7 @@ to {
124
126
  border-radius: 2px;
125
127
  transition: width 0.1s;
126
128
  }
127
- .gt-progress-played[data-v-00879d38] {
129
+ .gt-progress-played[data-v-957d0fbe] {
128
130
  position: absolute;
129
131
  top: 0;
130
132
  left: 0;
@@ -133,7 +135,7 @@ to {
133
135
  border-radius: 2px;
134
136
  transition: width 0.1s;
135
137
  }
136
- .gt-progress-handle[data-v-00879d38] {
138
+ .gt-progress-handle[data-v-957d0fbe] {
137
139
  position: absolute;
138
140
  right: -6px;
139
141
  top: 50%;
@@ -146,11 +148,11 @@ to {
146
148
  opacity: 0;
147
149
  transition: opacity 0.2s, transform 0.2s;
148
150
  }
149
- .gt-progress-container:hover .gt-progress-handle[data-v-00879d38] {
151
+ .gt-progress-container:hover .gt-progress-handle[data-v-957d0fbe] {
150
152
  opacity: 1;
151
153
  transform: translateY(-50%) scale(1.2);
152
154
  }
153
- .gt-progress-tooltip[data-v-00879d38] {
155
+ .gt-progress-tooltip[data-v-957d0fbe] {
154
156
  position: absolute;
155
157
  bottom: calc(100% + 8px);
156
158
  transform: translateX(-50%);
@@ -162,20 +164,20 @@ to {
162
164
  white-space: nowrap;
163
165
  pointer-events: none;
164
166
  }
165
- .gt-time-display[data-v-00879d38] {
167
+ .gt-time-display[data-v-957d0fbe] {
166
168
  color: white;
167
169
  font-size: 12px;
168
170
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
169
171
  white-space: nowrap;
170
172
  flex-shrink: 0;
171
173
  }
172
- .gt-time-separator[data-v-00879d38] {
174
+ .gt-time-separator[data-v-957d0fbe] {
173
175
  margin: 0 4px;
174
176
  opacity: 0.6;
175
177
  }
176
178
 
177
179
  /* 直播指示器 */
178
- .gt-live-indicator[data-v-00879d38] {
180
+ .gt-live-indicator[data-v-957d0fbe] {
179
181
  display: flex;
180
182
  align-items: center;
181
183
  gap: 6px;
@@ -183,17 +185,17 @@ to {
183
185
  font-size: 12px;
184
186
  font-weight: 500;
185
187
  }
186
- .gt-live-dot[data-v-00879d38] {
188
+ .gt-live-dot[data-v-957d0fbe] {
187
189
  width: 8px;
188
190
  height: 8px;
189
191
  background: #ff2d55;
190
192
  border-radius: 50%;
191
- animation: gt-live-pulse-00879d38 1.5s ease-in-out infinite;
193
+ animation: gt-live-pulse-957d0fbe 1.5s ease-in-out infinite;
192
194
  }
193
- .gt-live-text[data-v-00879d38] {
195
+ .gt-live-text[data-v-957d0fbe] {
194
196
  color: #ff2d55;
195
197
  }
196
- @keyframes gt-live-pulse-00879d38 {
198
+ @keyframes gt-live-pulse-957d0fbe {
197
199
  0%, 100% {
198
200
  opacity: 1;
199
201
  }
@@ -203,7 +205,7 @@ to {
203
205
  }
204
206
 
205
207
  /* 居中播放按钮 */
206
- .gt-center-play-btn[data-v-00879d38] {
208
+ .gt-center-play-btn[data-v-957d0fbe] {
207
209
  position: absolute;
208
210
  top: 50%;
209
211
  left: 50%;
@@ -218,11 +220,11 @@ to {
218
220
  cursor: pointer;
219
221
  transition: all 0.2s ease;
220
222
  }
221
- .gt-center-play-btn[data-v-00879d38]:hover {
223
+ .gt-center-play-btn[data-v-957d0fbe]:hover {
222
224
  background: rgba(0, 0, 0, 0.7);
223
225
  transform: translate(-50%, -50%) scale(1.05);
224
226
  }
225
- .gt-center-play-btn svg[data-v-00879d38] {
227
+ .gt-center-play-btn svg[data-v-957d0fbe] {
226
228
  width: 24px;
227
229
  height: 24px;
228
230
  color: white;
@@ -230,7 +232,7 @@ to {
230
232
  }
231
233
 
232
234
  /* 环境信息面板 */
233
- .gt-env-panel[data-v-00879d38] {
235
+ .gt-env-panel[data-v-957d0fbe] {
234
236
  position: absolute;
235
237
  top: 12px;
236
238
  left: 12px;
@@ -244,46 +246,46 @@ to {
244
246
  max-height: calc(100% - 24px);
245
247
  overflow-y: auto;
246
248
  backdrop-filter: blur(10px);
247
- animation: gt-env-fade-in-00879d38 0.15s ease;
249
+ animation: gt-env-fade-in-957d0fbe 0.15s ease;
248
250
  z-index: 15;
249
251
  }
250
- .gt-env-panel[data-v-00879d38]::-webkit-scrollbar {
252
+ .gt-env-panel[data-v-957d0fbe]::-webkit-scrollbar {
251
253
  width: 4px;
252
254
  }
253
- .gt-env-panel[data-v-00879d38]::-webkit-scrollbar-track {
255
+ .gt-env-panel[data-v-957d0fbe]::-webkit-scrollbar-track {
254
256
  background: transparent;
255
257
  }
256
- .gt-env-panel[data-v-00879d38]::-webkit-scrollbar-thumb {
258
+ .gt-env-panel[data-v-957d0fbe]::-webkit-scrollbar-thumb {
257
259
  background: rgba(255, 255, 255, 0.3);
258
260
  border-radius: 2px;
259
261
  }
260
- @keyframes gt-env-fade-in-00879d38 {
262
+ @keyframes gt-env-fade-in-957d0fbe {
261
263
  from { opacity: 0; transform: translateY(-4px);
262
264
  }
263
265
  to { opacity: 1; transform: translateY(0);
264
266
  }
265
267
  }
266
- .gt-env-panel .gt-env-row[data-v-00879d38] {
268
+ .gt-env-panel .gt-env-row[data-v-957d0fbe] {
267
269
  display: flex;
268
270
  justify-content: space-between;
269
271
  align-items: center;
270
272
  padding: 2px 0;
271
273
  color: rgba(255, 255, 255, 0.7);
272
274
  }
273
- .gt-env-panel .gt-env-row span[data-v-00879d38]:first-child {
275
+ .gt-env-panel .gt-env-row span[data-v-957d0fbe]:first-child {
274
276
  color: rgba(255, 255, 255, 0.5);
275
277
  margin-right: 16px;
276
278
  }
277
- .gt-env-panel .gt-env-row span[data-v-00879d38]:last-child {
279
+ .gt-env-panel .gt-env-row span[data-v-957d0fbe]:last-child {
278
280
  font-variant-numeric: tabular-nums;
279
281
  }
280
- .gt-env-panel .gt-env-row .ok[data-v-00879d38] { color: #4caf50;
282
+ .gt-env-panel .gt-env-row .ok[data-v-957d0fbe] { color: #4caf50;
281
283
  }
282
- .gt-env-panel .gt-env-row .warn[data-v-00879d38] { color: #ff9800;
284
+ .gt-env-panel .gt-env-row .warn[data-v-957d0fbe] { color: #ff9800;
283
285
  }
284
- .gt-env-panel .gt-env-row .err[data-v-00879d38] { color: #f44336;
286
+ .gt-env-panel .gt-env-row .err[data-v-957d0fbe] { color: #f44336;
285
287
  }
286
- .gt-env-divider[data-v-00879d38] {
288
+ .gt-env-divider[data-v-957d0fbe] {
287
289
  height: 1px;
288
290
  background: rgba(255, 255, 255, 0.1);
289
291
  margin: 4px 0;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @give-tech/ec-player-vue
3
+ *
4
+ * Vue 3 播放器组件
5
+ */
6
+ export { default as EcPlayer } from './components/EcPlayer.vue';
7
+ export type { EcPlayerProps, EcPlayerEmits, EcPlayerExpose, EcPlayerSource, EcPlayerControlOption, EnvPanelData, EcPlayerGlobalConfig, WasmPathConfig, PlayerOptions } from './types';
8
+ export { configureEcPlayer, getGlobalConfig, getWasmConfig, getMergedConfig, getMergedPlayerOptions, resolveWasmPath, DEFAULT_EC_PLAYER_PROPS, DEFAULT_PLAYER_OPTIONS, DEFAULT_WASM_CONFIG, LogLevel } from './types';
9
+ export { usePlayer, useControls, useEnvInfo, type UsePlayerReturn, type UseControlsParams, type UseControlsReturn, type UseEnvInfoReturn } from './composables';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAG/D,YAAY,EACV,aAAa,EACb,aAAa,EACb,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,aAAa,EACd,MAAM,SAAS,CAAA;AAGhB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,uBAAuB,EACvB,sBAAsB,EACtB,mBAAmB,EACnB,QAAQ,EACT,MAAM,SAAS,CAAA;AAGhB,OAAO,EACL,SAAS,EACT,WAAW,EACX,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAA"}
package/dist/index.js CHANGED
@@ -77,6 +77,7 @@ function usePlayer(emit) {
77
77
  const isLoading = ref(false);
78
78
  const detectedFormat = ref("");
79
79
  const isLive = ref(false);
80
+ let playRequestId = 0;
80
81
  const globalConfig2 = getGlobalConfig();
81
82
  const currentOptions = reactive({
82
83
  targetBufferSize: globalConfig2.targetBufferSize ?? DEFAULT_PLAYER_OPTIONS.targetBufferSize,
@@ -119,6 +120,8 @@ function usePlayer(emit) {
119
120
  console.error("[EcPlayer] URL is required");
120
121
  return;
121
122
  }
123
+ const currentRequestId = ++playRequestId;
124
+ console.log("[EcPlayer] play() called, requestId:", currentRequestId);
122
125
  if (isLoading.value && player.value) {
123
126
  console.log("[EcPlayer] Aborting current load to start new one...");
124
127
  player.value.destroy();
@@ -129,6 +132,10 @@ function usePlayer(emit) {
129
132
  await new Promise((resolve) => setTimeout(resolve, 100));
130
133
  retries--;
131
134
  }
135
+ if (currentRequestId !== playRequestId) {
136
+ console.log("[EcPlayer] Request obsolete after waiting for canvas, aborting");
137
+ return;
138
+ }
132
139
  if (!canvasRef.value) {
133
140
  console.error("[EcPlayer] Canvas element not found");
134
141
  return;
@@ -160,6 +167,14 @@ function usePlayer(emit) {
160
167
  };
161
168
  player.value = new EcPlayerCore(config, callbacks);
162
169
  await player.value.load(url, isLiveParam);
170
+ if (currentRequestId !== playRequestId) {
171
+ console.log("[EcPlayer] Request obsolete after load, aborting");
172
+ if (player.value) {
173
+ player.value.destroy();
174
+ player.value = null;
175
+ }
176
+ return;
177
+ }
163
178
  detectedFormat.value = player.value.getDetectedFormat();
164
179
  state.isLoaded = true;
165
180
  await player.value.play();
@@ -167,7 +182,12 @@ function usePlayer(emit) {
167
182
  } catch (error) {
168
183
  console.error("[EcPlayer] Failed to play:", error?.message || error);
169
184
  } finally {
170
- isLoading.value = false;
185
+ if (currentRequestId === playRequestId) {
186
+ isLoading.value = false;
187
+ console.log("[EcPlayer] play() finished, requestId:", currentRequestId);
188
+ } else {
189
+ console.log("[EcPlayer] play() finished but ignored (obsolete), requestId:", currentRequestId, "current:", playRequestId);
190
+ }
171
191
  }
172
192
  }
173
193
  function setOptions(options) {
@@ -735,7 +755,7 @@ const _export_sfc = (sfc, props) => {
735
755
  }
736
756
  return target;
737
757
  };
738
- const EcPlayer = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-00879d38"]]);
758
+ const EcPlayer = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-957d0fbe"]]);
739
759
  export {
740
760
  DEFAULT_EC_PLAYER_PROPS,
741
761
  DEFAULT_PLAYER_OPTIONS,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/types.ts","../src/composables/usePlayer.ts","../src/composables/useControls.ts","../src/composables/useEnvInfo.ts","../src/components/EcPlayer.vue"],"sourcesContent":["/**\n * Vue 组件类型定义\n */\n\nimport type { EcPlayerCoreConfig, EcPlayerState, EnvInfo } from '@give-tech/ec-player'\nexport { LogLevel } from '@give-tech/ec-player'\n\n/**\n * WASM 路径配置\n */\nexport interface WasmPathConfig {\n /**\n * SIMD 版本 WASM 路径(推荐,性能更好)\n * 用于支持 SIMD 的现代浏览器\n */\n simd?: string\n /**\n * 非 SIMD 版本 WASM 路径\n * 用于不支持 SIMD 的旧浏览器作为降级方案\n */\n nonSimd?: string\n}\n\n/**\n * 全局配置\n *\n * 必须在使用 EcPlayer 组件前调用\n *\n * @example\n * ```typescript\n * // main.ts\n * import { configureEcPlayer } from '@give-tech/ec-player-vue'\n *\n * // 配置 SIMD 和非 SIMD 路径,支持自动降级\n * configureEcPlayer({\n * wasm: {\n * simd: '/wasm/decoder-simd.js',\n * nonSimd: '/wasm/decoder.js'\n * }\n * })\n *\n * // 使用 CDN\n * configureEcPlayer({\n * wasm: {\n * simd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder-simd.js',\n * nonSimd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder.js'\n * }\n * })\n * ```\n */\nexport interface EcPlayerGlobalConfig {\n /**\n * WASM 路径配置(必填)\n * 支持 SIMD 和非 SIMD 自动降级\n */\n wasm?: WasmPathConfig\n /** 默认是否显示控制条 */\n showControls?: boolean\n /** 默认缓冲目标帧数 */\n targetBufferSize?: number\n /** 默认每批解码帧数 */\n decodeBatchSize?: number\n /** 默认队列上限 */\n maxQueueSize?: number\n}\n\n/**\n * 播放器配置选项\n */\nexport interface PlayerOptions {\n /** 帧缓冲区目标大小 */\n targetBufferSize?: number\n /** 每批解码帧数 */\n decodeBatchSize?: number\n /** NAL/Sample 队列上限 */\n maxQueueSize?: number\n}\n\n/**\n * 视频源配置\n */\nexport interface EcPlayerSource {\n /** 视频 URL */\n url: string\n /** 是否为直播流 */\n isLive?: boolean\n /** 版本号,用于强制重新加载 */\n version?: number\n}\n\n/**\n * 控制条选项\n */\nexport interface EcPlayerControlOption {\n /** 是否显示控制条 */\n show?: boolean\n /** 是否显示全屏按钮 */\n fullscreen?: boolean\n /** 是否显示环境信息按钮 */\n info?: boolean\n}\n\n/**\n * EcPlayer 组件 Props\n */\nexport interface EcPlayerProps {\n /** 视频源 */\n src?: EcPlayerSource\n /** 控制条选项 */\n controlOption?: EcPlayerControlOption\n}\n\n/**\n * EcPlayer 组件 Emits\n */\nexport interface EcPlayerEmits {\n /** 播放时触发 */\n (e: 'play'): void\n /** 暂停时触发 */\n (e: 'pause'): void\n}\n\n/**\n * EcPlayer 组件 Expose 方法\n */\nexport interface EcPlayerExpose {\n /** 播放 */\n play: () => void\n /** 暂停 */\n pause: () => void\n /** 跳转到指定时间(毫秒) */\n seek: (time: number) => Promise<void>\n}\n\n/**\n * 环境信息面板数据\n */\nexport interface EnvPanelData {\n envInfo: EnvInfo | null\n state: Partial<EcPlayerState>\n}\n\n/**\n * 播放器配置(从 Props 转换)\n */\nexport interface PlayerConfigFromProps extends EcPlayerCoreConfig {\n wasmPath: string\n targetBufferSize: number\n decodeBatchSize: number\n maxQueueSize: number\n}\n\n/**\n * 默认 Props 值\n */\nexport const DEFAULT_EC_PLAYER_PROPS = {\n src: undefined as EcPlayerSource | undefined,\n controlOption: {\n show: true,\n fullscreen: true,\n info: true\n }\n}\n\n/**\n * 默认播放器配置\n */\nexport const DEFAULT_PLAYER_OPTIONS: Required<PlayerOptions> = {\n targetBufferSize: 60,\n decodeBatchSize: 2,\n maxQueueSize: 200\n}\n\n/**\n * 默认 WASM 配置\n */\nexport const DEFAULT_WASM_CONFIG: WasmPathConfig = {\n simd: '/wasm/decoder-simd.js',\n nonSimd: '/wasm/decoder.js'\n}\n\n/**\n * 全局配置存储\n */\nlet globalConfig: EcPlayerGlobalConfig = {}\n\n/**\n * 配置 EcPlayer 全局默认值\n *\n * @example\n * ```typescript\n * // main.ts\n * import { configureEcPlayer } from '@give-tech/ec-player-vue'\n *\n * // 配置 SIMD 和非 SIMD 路径,支持自动降级\n * configureEcPlayer({\n * wasm: {\n * simd: '/wasm/decoder-simd.js',\n * nonSimd: '/wasm/decoder.js'\n * }\n * })\n *\n * // 使用 CDN\n * configureEcPlayer({\n * wasm: {\n * simd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder-simd.js',\n * nonSimd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder.js'\n * }\n * })\n * ```\n */\nexport function configureEcPlayer(config: EcPlayerGlobalConfig): void {\n globalConfig = { ...globalConfig, ...config }\n}\n\n/**\n * 获取全局配置\n */\nexport function getGlobalConfig(): EcPlayerGlobalConfig {\n return { ...globalConfig }\n}\n\n/**\n * 获取 WASM 路径配置\n */\nexport function getWasmConfig(): WasmPathConfig {\n return {\n simd: globalConfig.wasm?.simd ?? DEFAULT_WASM_CONFIG.simd,\n nonSimd: globalConfig.wasm?.nonSimd ?? DEFAULT_WASM_CONFIG.nonSimd\n }\n}\n\n/**\n * 获取合并后的配置(props > 全局配置 > 默认值)\n */\nexport function getMergedConfig(props: EcPlayerProps) {\n return {\n src: props.src ?? DEFAULT_EC_PLAYER_PROPS.src,\n controlOption: {\n show: props.controlOption?.show ?? globalConfig.showControls ?? DEFAULT_EC_PLAYER_PROPS.controlOption.show,\n fullscreen: props.controlOption?.fullscreen ?? DEFAULT_EC_PLAYER_PROPS.controlOption.fullscreen,\n info: props.controlOption?.info ?? DEFAULT_EC_PLAYER_PROPS.controlOption.info\n }\n }\n}\n\n/**\n * 获取合并后的播放器配置(options > 全局配置 > 默认值)\n */\nexport function getMergedPlayerOptions(options: PlayerOptions): Required<PlayerOptions> {\n return {\n targetBufferSize: options.targetBufferSize ?? globalConfig.targetBufferSize ?? DEFAULT_PLAYER_OPTIONS.targetBufferSize,\n decodeBatchSize: options.decodeBatchSize ?? globalConfig.decodeBatchSize ?? DEFAULT_PLAYER_OPTIONS.decodeBatchSize,\n maxQueueSize: options.maxQueueSize ?? globalConfig.maxQueueSize ?? DEFAULT_PLAYER_OPTIONS.maxQueueSize\n }\n}\n\n/**\n * 根据环境能力选择合适的 WASM 路径\n *\n * @param hasSimdSupport 是否支持 SIMD\n * @returns 实际使用的 WASM 路径\n */\nexport function resolveWasmPath(hasSimdSupport: boolean): string {\n const wasmConfig = getWasmConfig()\n\n // 根据 SIMD 支持情况选择\n if (hasSimdSupport && wasmConfig.simd) {\n console.log('[EcPlayer] Using SIMD WASM:', wasmConfig.simd)\n return wasmConfig.simd\n }\n\n if (wasmConfig.nonSimd) {\n console.log('[EcPlayer] SIMD not supported, using non-SIMD WASM:', wasmConfig.nonSimd)\n return wasmConfig.nonSimd\n }\n\n // 降级到 SIMD 版本(兜底)\n console.log('[EcPlayer] No non-SIMD path configured, falling back to SIMD:', wasmConfig.simd)\n return wasmConfig.simd!\n}\n","/**\n * 播放器核心逻辑\n */\n\nimport { ref, shallowRef, reactive, onUnmounted, type Ref } from 'vue'\nimport {\n EcPlayerCore,\n EnvDetector,\n setLogLevel as coreSetLogLevel,\n type EcPlayerCoreConfig,\n type EcPlayerState,\n type EcPlayerCallbacks\n} from '@give-tech/ec-player'\nimport type { EcPlayerProps, PlayerOptions } from '../types'\nimport { resolveWasmPath, DEFAULT_PLAYER_OPTIONS, getGlobalConfig } from '../types'\n\n/**\n * 播放器逻辑返回值\n */\nexport interface UsePlayerReturn {\n /** Canvas 元素引用 */\n canvasRef: Ref<HTMLCanvasElement | null>\n /** 播放器实例 */\n player: Ref<EcPlayerCore | null>\n /** 响应式状态 */\n state: EcPlayerState\n /** 是否正在加载 */\n isLoading: Ref<boolean>\n /** 检测到的格式 */\n detectedFormat: Ref<string>\n /** 是否为直播流 */\n isLive: Ref<boolean>\n /** 播放 */\n play: (options: { url: string; isLive?: boolean }) => Promise<void>\n /** 设置配置 */\n setOptions: (options: PlayerOptions) => void\n /** 设置日志级别 */\n setLogLevel: (level: number) => void\n /** 暂停 */\n pause: () => void\n /** 恢复播放 */\n resume: () => Promise<void>\n /** 跳转 */\n seek: (time: number) => Promise<void>\n /** 获取播放器实例 */\n getPlayer: () => EcPlayerCore | null\n /** 获取状态 */\n getState: () => EcPlayerState\n /** 获取当前时间 */\n getCurrentTime: () => number\n /** 获取总时长 */\n getDuration: () => number\n /** 销毁播放器 */\n destroy: () => void\n}\n\n/**\n * 从配置选项创建播放器配置\n */\nfunction createConfig(options: Required<PlayerOptions>): Omit<EcPlayerCoreConfig, 'isLive'> {\n // 检测 SIMD 支持\n const envInfo = EnvDetector.detect()\n const wasmPath = resolveWasmPath(envInfo.capabilities.wasmSimd)\n\n return {\n wasmPath,\n targetBufferSize: options.targetBufferSize,\n decodeBatchSize: options.decodeBatchSize,\n maxQueueSize: options.maxQueueSize\n }\n}\n\n/**\n * 播放器逻辑 Hook\n */\nexport function usePlayer(\n emit: {\n (e: 'play'): void\n (e: 'pause'): void\n }\n): UsePlayerReturn {\n const canvasRef = ref<HTMLCanvasElement | null>(null)\n const player = shallowRef<EcPlayerCore | null>(null)\n const isLoading = ref(false)\n const detectedFormat = ref('')\n const isLive = ref(false) // 跟踪当前是否为直播流\n\n // 当前播放器配置(初始化时合并全局配置和默认值)\n const globalConfig = getGlobalConfig()\n const currentOptions = reactive<Required<PlayerOptions>>({\n targetBufferSize: globalConfig.targetBufferSize ?? DEFAULT_PLAYER_OPTIONS.targetBufferSize,\n decodeBatchSize: globalConfig.decodeBatchSize ?? DEFAULT_PLAYER_OPTIONS.decodeBatchSize,\n maxQueueSize: globalConfig.maxQueueSize ?? DEFAULT_PLAYER_OPTIONS.maxQueueSize\n })\n\n // 响应式状态\n const state = reactive<EcPlayerState>({\n isPlaying: false,\n isLoaded: false,\n isLoading: false,\n fps: 0,\n resolution: '-',\n decoded: 0,\n downloaded: 0,\n droppedFrames: 0,\n isPrefetching: false,\n segmentIndex: 0,\n fetchedSegmentCount: 0,\n totalSegments: 0,\n downloadSpeed: 0,\n currentTime: 0,\n duration: 0\n })\n\n // 创建回调\n const callbacks: EcPlayerCallbacks = {\n onStateChange: (partial) => {\n Object.assign(state, partial)\n\n // 派发 play/pause 事件\n if (partial.isPlaying === true) {\n emit('play')\n } else if (partial.isPlaying === false && state.isPlaying) {\n emit('pause')\n }\n },\n onError: (error) => {\n console.error('[EcPlayer] Error:', error)\n }\n }\n\n // 播放(加载并播放)\n async function play(options: { url: string; isLive?: boolean }): Promise<void> {\n const { url, isLive: isLiveParam = false } = options\n if (!url) {\n console.error('[EcPlayer] URL is required')\n return\n }\n\n // 如果正在加载中,先销毁当前播放器\n if (isLoading.value && player.value) {\n console.log('[EcPlayer] Aborting current load to start new one...')\n player.value.destroy()\n player.value = null\n }\n\n // 等待 canvas 准备好\n let retries = 10\n while (!canvasRef.value && retries > 0) {\n await new Promise(resolve => setTimeout(resolve, 100))\n retries--\n }\n\n if (!canvasRef.value) {\n console.error('[EcPlayer] Canvas element not found')\n return\n }\n\n isLoading.value = true\n isLive.value = isLiveParam\n\n try {\n // 销毁旧播放器\n if (player.value) {\n player.value.destroy()\n player.value = null\n }\n\n // 重置状态\n state.isPlaying = false\n state.isLoaded = false\n state.fps = 0\n state.resolution = '-'\n state.decoded = 0\n state.downloaded = 0\n state.droppedFrames = 0\n state.segmentIndex = 0\n state.totalSegments = 0\n state.downloadSpeed = 0\n state.currentTime = 0\n state.duration = 0\n detectedFormat.value = ''\n\n // 创建配置\n const config: EcPlayerCoreConfig & { canvas: HTMLCanvasElement } = {\n ...createConfig(currentOptions),\n canvas: canvasRef.value,\n isLive: isLiveParam\n }\n\n // 创建播放器\n player.value = new EcPlayerCore(config, callbacks)\n\n // 加载视频\n await player.value.load(url, isLiveParam)\n\n // 获取检测到的格式\n detectedFormat.value = player.value.getDetectedFormat()\n\n // 标记加载完成\n state.isLoaded = true\n\n // 开始播放\n await player.value.play()\n\n console.log('[EcPlayer] Playing stream, format:', detectedFormat.value)\n } catch (error: any) {\n console.error('[EcPlayer] Failed to play:', error?.message || error)\n } finally {\n isLoading.value = false\n }\n }\n\n // 设置配置\n function setOptions(options: PlayerOptions): void {\n if (options.targetBufferSize !== undefined) {\n currentOptions.targetBufferSize = options.targetBufferSize\n }\n if (options.decodeBatchSize !== undefined) {\n currentOptions.decodeBatchSize = options.decodeBatchSize\n }\n if (options.maxQueueSize !== undefined) {\n currentOptions.maxQueueSize = options.maxQueueSize\n }\n console.log('[EcPlayer] Options updated:', currentOptions)\n }\n\n // 设置日志级别\n function setLogLevel(level: number): void {\n coreSetLogLevel(level)\n }\n\n // 暂停\n function pause(): void {\n player.value?.pause()\n }\n\n // 恢复播放\n async function resume(): Promise<void> {\n if (!player.value) return\n await player.value.play()\n }\n\n // 跳转\n async function seek(time: number): Promise<void> {\n if (!player.value) return\n await player.value.seek(time)\n }\n\n // 获取播放器实例\n function getPlayer(): EcPlayerCore | null {\n return player.value\n }\n\n // 获取状态\n function getState(): EcPlayerState {\n return player.value?.getState() ?? { ...state }\n }\n\n // 获取当前时间\n function getCurrentTime(): number {\n return player.value?.getCurrentTime() ?? 0\n }\n\n // 获取总时长\n function getDuration(): number {\n return player.value?.getDuration() ?? 0\n }\n\n // 销毁播放器\n function destroy(): void {\n if (player.value) {\n player.value.destroy()\n player.value = null\n }\n }\n\n // 组件卸载时销毁\n onUnmounted(() => {\n destroy()\n })\n\n return {\n canvasRef,\n player,\n state,\n isLoading,\n detectedFormat,\n isLive,\n play,\n setOptions,\n setLogLevel,\n pause,\n resume,\n seek,\n getPlayer,\n getState,\n getCurrentTime,\n getDuration,\n destroy\n }\n}\n","/**\n * 播放器控制条逻辑\n */\n\nimport { ref, computed, type Ref } from 'vue'\n\n/**\n * 控制条逻辑参数\n */\nexport interface UseControlsParams {\n /** 是否正在播放 */\n isPlaying: Ref<boolean>\n /** 是否已加载 */\n isLoaded: Ref<boolean>\n /** 当前时间(毫秒) */\n currentTime: Ref<number>\n /** 总时长(毫秒) */\n duration: Ref<number>\n /** 已解析的分片索引 */\n segmentIndex: Ref<number>\n /** 已下载的分片总数 */\n fetchedSegmentCount: Ref<number>\n /** 总分片数 */\n totalSegments: Ref<number>\n /** 是否为直播流 */\n isLive: Ref<boolean>\n /** 跳转方法 */\n seek: (time: number) => Promise<void>\n}\n\n/**\n * 控制条逻辑返回值\n */\nexport interface UseControlsReturn {\n /** 是否显示控制条 */\n showControls: Ref<boolean>\n /** 进度条 hover 时间 */\n hoverTime: Ref<number | null>\n /** 进度条 hover 位置 */\n hoverPosition: Ref<number>\n /** 播放进度百分比 */\n playProgress: Ref<number>\n /** 缓冲进度百分比 */\n bufferProgress: Ref<number>\n /** 显示控制条 */\n showControlsBar: () => void\n /** 隐藏控制条 */\n hideControlsBar: () => void\n /** 处理进度条点击 */\n handleProgressClick: (event: MouseEvent) => Promise<void>\n /** 处理进度条悬停 */\n handleProgressHover: (event: MouseEvent) => void\n /** 格式化时间 */\n formatTime: (ms: number) => string\n}\n\n/**\n * 控制条逻辑 Hook\n */\nexport function useControls(params: UseControlsParams): UseControlsReturn {\n let hideControlsTimer: number | null = null\n const {\n isPlaying,\n isLoaded,\n currentTime,\n duration,\n segmentIndex,\n fetchedSegmentCount,\n totalSegments,\n isLive,\n seek\n } = params\n\n const showControls = ref(true)\n const hoverTime = ref<number | null>(null)\n const hoverPosition = ref(0)\n\n // 计算播放进度\n const playProgress = computed(() => {\n if (duration.value === 0) return 0\n return (currentTime.value / duration.value) * 100\n })\n\n // 计算缓冲进度(已下载的分段数)\n const bufferProgress = computed(() => {\n if (totalSegments.value === 0) return 0\n return (fetchedSegmentCount.value / totalSegments.value) * 100\n })\n\n // 显示控制条\n function showControlsBar(): void {\n showControls.value = true\n if (hideControlsTimer) {\n clearTimeout(hideControlsTimer)\n }\n hideControlsTimer = window.setTimeout(() => {\n if (isPlaying.value) {\n showControls.value = false\n }\n }, 3000)\n }\n\n // 隐藏控制条\n function hideControlsBar(): void {\n if (isPlaying.value) {\n showControls.value = false\n }\n }\n\n // 处理进度条点击\n async function handleProgressClick(event: MouseEvent): Promise<void> {\n if (duration.value === 0) return\n\n const target = event.currentTarget as HTMLElement\n const rect = target.getBoundingClientRect()\n const clickX = event.clientX - rect.left\n const percentage = clickX / rect.width\n const targetTime = percentage * duration.value\n\n await seek(targetTime)\n }\n\n // 处理进度条悬停\n function handleProgressHover(event: MouseEvent): void {\n if (duration.value === 0) return\n\n const target = event.currentTarget as HTMLElement\n const rect = target.getBoundingClientRect()\n const hoverX = event.clientX - rect.left\n const percentage = Math.max(0, Math.min(1, hoverX / rect.width))\n\n hoverTime.value = percentage * duration.value\n hoverPosition.value = percentage * 100\n }\n\n // 格式化时间\n function formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`\n }\n return `${minutes}:${seconds.toString().padStart(2, '0')}`\n }\n\n return {\n showControls,\n hoverTime,\n hoverPosition,\n playProgress,\n bufferProgress,\n showControlsBar,\n hideControlsBar,\n handleProgressClick,\n handleProgressHover,\n formatTime\n }\n}\n","/**\n * 环境信息逻辑\n */\n\nimport { ref, computed, onMounted, type Ref } from 'vue'\nimport { EnvDetector, type EnvInfo } from '@give-tech/ec-player'\n\n/**\n * 环境信息逻辑返回值\n */\nexport interface UseEnvInfoReturn {\n /** 环境信息 */\n envInfo: Ref<EnvInfo | null>\n /** 是否显示环境面板 */\n showEnvPanel: Ref<boolean>\n /** 环境类型文本 */\n envTypeText: Ref<string>\n /** 切换环境面板显示 */\n toggleEnvPanel: () => void\n /** 检测环境 */\n detectEnv: () => void\n}\n\n/**\n * 环境信息逻辑 Hook\n */\nexport function useEnvInfo(\n initialShowPanel: boolean = false\n): UseEnvInfoReturn {\n const envInfo = ref<EnvInfo | null>(null)\n const showEnvPanel = ref(initialShowPanel)\n\n // 环境类型文本\n const envTypeText = computed(() => {\n if (!envInfo.value) return '未知'\n const typeMap: Record<string, string> = {\n 'pc-browser': 'PC 浏览器',\n 'mobile-browser': '移动端浏览器',\n 'wechat-miniprogram': '微信小程序',\n 'alipay-miniprogram': '支付宝小程序',\n 'other-miniprogram': '其他小程序',\n 'unknown': '未知',\n }\n return typeMap[envInfo.value.platform] || envInfo.value.platform\n })\n\n // 切换环境面板显示\n function toggleEnvPanel(): void {\n showEnvPanel.value = !showEnvPanel.value\n }\n\n // 检测环境\n function detectEnv(): void {\n envInfo.value = EnvDetector.detect()\n console.log('[EcPlayer] 环境检测结果:', envInfo.value)\n }\n\n // 组件挂载时检测环境\n onMounted(() => {\n detectEnv()\n })\n\n return {\n envInfo,\n showEnvPanel,\n envTypeText,\n toggleEnvPanel,\n detectEnv\n }\n}\n","<template>\n <div class=\"gt-player-container\" ref=\"containerRef\">\n <div\n class=\"gt-video-wrapper\"\n ref=\"videoWrapperRef\"\n @mouseenter=\"controls.showControlsBar\"\n @mousemove=\"controls.showControlsBar\"\n @mouseleave=\"controls.hideControlsBar\"\n >\n <!-- Canvas 视频渲染 -->\n <canvas ref=\"canvasRef\" class=\"gt-player-canvas\"></canvas>\n\n <!-- 封面/占位内容(未加载时显示) -->\n <div v-if=\"!player.state.isLoaded\" class=\"gt-placeholder\">\n <slot name=\"placeholder\"></slot>\n </div>\n\n <!-- 环境信息面板 -->\n <div v-if=\"showEnvPanel && envInfo\" class=\"gt-env-panel\">\n <div class=\"gt-env-row\">\n <span>platform</span>\n <span :class=\"envInfo.isMiniProgram ? 'warn' : ''\">{{ envTypeText }}</span>\n </div>\n <div class=\"gt-env-divider\"></div>\n <div class=\"gt-env-row\">\n <span>SIMD</span>\n <span :class=\"envInfo.capabilities.wasmSimd ? 'ok' : 'err'\">\n {{ envInfo.capabilities.wasmSimd ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>WebGL</span>\n <span :class=\"envInfo.capabilities.webGL ? 'ok' : 'err'\">\n {{ envInfo.capabilities.webGL ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>WebGL2</span>\n <span :class=\"envInfo.capabilities.webGL2 ? 'ok' : 'err'\">\n {{ envInfo.capabilities.webGL2 ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>SAB</span>\n <span :class=\"envInfo.capabilities.sharedArrayBuffer ? 'ok' : 'err'\">\n {{ envInfo.capabilities.sharedArrayBuffer ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>Threads</span>\n <span :class=\"envInfo.capabilities.wasmThreads ? 'ok' : 'err'\">\n {{ envInfo.capabilities.wasmThreads ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>WebAudio</span>\n <span :class=\"envInfo.capabilities.webAudio ? 'ok' : 'err'\">\n {{ envInfo.capabilities.webAudio ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>Stream</span>\n <span :class=\"envInfo.capabilities.readableStream ? 'ok' : 'err'\">\n {{ envInfo.capabilities.readableStream ? 'Yes' : 'No' }}\n </span>\n </div>\n <template v-if=\"player.state.isLoaded\">\n <div class=\"gt-env-divider\"></div>\n <div class=\"gt-env-row\" v-if=\"player.detectedFormat.value\"><span>Format</span><span>{{ player.detectedFormat.value }}</span></div>\n <div class=\"gt-env-row\"><span>FPS</span><span>{{ player.state.fps }}</span></div>\n <div class=\"gt-env-row\"><span>Resolution</span><span>{{ player.state.resolution }}</span></div>\n <div class=\"gt-env-row\"><span>downloaded</span><span>{{ player.state.downloaded }}</span></div>\n <div class=\"gt-env-row\"><span>decoded</span><span>{{ player.state.decoded }}</span></div>\n <div class=\"gt-env-row\"><span>Dropped</span><span>{{ player.state.droppedFrames }}</span></div>\n <div class=\"gt-env-row\"><span>Speed</span><span>{{ player.state.downloadSpeed || 0 }} KB/s</span></div>\n </template>\n </div>\n\n <!-- 加载中动画 -->\n <div class=\"gt-loading-spinner\" v-if=\"player.isLoading.value\">\n <slot name=\"loading\">\n <div class=\"gt-spinner\"></div>\n </slot>\n </div>\n\n <!-- 视频控制条 -->\n <div\n v-if=\"controlOption.show\"\n class=\"gt-video-controls\"\n :class=\"{ visible: controls.showControls.value || !player.state.isPlaying }\"\n >\n <button class=\"gt-control-btn\" @click=\"togglePlayback\">\n <svg v-if=\"player.state.isPlaying\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n </button>\n\n <!-- 直播标识 -->\n <span class=\"gt-live-indicator\" v-if=\"isLive\">\n <span class=\"gt-live-dot\"></span>\n <span class=\"gt-live-text\">实时</span>\n </span>\n\n <!-- 点播时间显示 -->\n <span class=\"gt-time-display\" v-else>\n <span class=\"gt-time-current\">{{ controls.formatTime(player.state.currentTime) }}</span>\n <span class=\"gt-time-separator\">/</span>\n <span class=\"gt-time-duration\">{{ controls.formatTime(player.state.duration) }}</span>\n </span>\n\n <!-- 进度条 (仅点播) -->\n <div\n v-if=\"!isLive\"\n class=\"gt-progress-container\"\n @click=\"controls.handleProgressClick\"\n @mousemove=\"controls.handleProgressHover\"\n @mouseleave=\"controls.hoverTime.value = null\"\n >\n <div class=\"gt-progress-bar\">\n <div class=\"gt-progress-buffered\" :style=\"{ width: controls.bufferProgress.value + '%' }\"></div>\n <div class=\"gt-progress-played\" :style=\"{ width: controls.playProgress.value + '%' }\">\n <div class=\"gt-progress-handle\"></div>\n </div>\n </div>\n <div\n class=\"gt-progress-tooltip\"\n v-if=\"controls.hoverTime.value !== null\"\n :style=\"{ left: controls.hoverPosition.value + '%' }\"\n >\n {{ controls.formatTime(controls.hoverTime.value) }}\n </div>\n </div>\n\n <!-- 直播流占位符 -->\n <div class=\"gt-flex-spacer\" v-else></div>\n\n <!-- 环境信息按钮 -->\n <button v-if=\"controlOption.info\" class=\"gt-control-btn gt-env-info-btn\" @click=\"toggleEnvPanel\" :title=\"showEnvPanel ? '隐藏信息' : '显示信息'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/>\n </svg>\n </button>\n\n <!-- 全屏按钮 -->\n <button v-if=\"controlOption.fullscreen\" class=\"gt-control-btn gt-fullscreen-btn\" @click=\"toggleFullscreen\" :title=\"isFullscreen ? '退出全屏' : '全屏'\">\n <!-- 退出全屏图标 -->\n <svg v-if=\"isFullscreen\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\"/>\n </svg>\n <!-- 全屏图标 -->\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\"/>\n </svg>\n </button>\n </div>\n\n <!-- 大播放按钮(居中) -->\n <div\n class=\"gt-center-play-btn\"\n v-if=\"player.state.isLoaded && !player.state.isPlaying\"\n @click=\"togglePlayback\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch, onMounted, onUnmounted } from 'vue'\nimport { usePlayer } from '../composables/usePlayer'\nimport { useControls } from '../composables/useControls'\nimport { useEnvInfo } from '../composables/useEnvInfo'\nimport type { EcPlayerProps, EcPlayerExpose, EcPlayerSource, EcPlayerControlOption } from '../types'\nimport { DEFAULT_EC_PLAYER_PROPS } from '../types'\n\n// Props\nconst props = withDefaults(defineProps<EcPlayerProps>(), DEFAULT_EC_PLAYER_PROPS)\n\n// Emits\nconst emit = defineEmits<{\n (e: 'play'): void\n (e: 'pause'): void\n}>()\n\n// Canvas ref(本地)\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\n\n// 容器引用(用于全屏)\nconst containerRef = ref<HTMLElement | null>(null)\n\n// 全屏状态\nconst isFullscreen = ref(false)\n\n// 解析 controlOption\nconst controlOption = computed<Required<EcPlayerControlOption>>(() => ({\n show: props.controlOption?.show ?? DEFAULT_EC_PLAYER_PROPS.controlOption.show,\n fullscreen: props.controlOption?.fullscreen ?? DEFAULT_EC_PLAYER_PROPS.controlOption.fullscreen,\n info: props.controlOption?.info ?? DEFAULT_EC_PLAYER_PROPS.controlOption.info\n}))\n\n// 环境信息逻辑\nconst { envInfo, showEnvPanel, envTypeText, toggleEnvPanel } = useEnvInfo(false)\n\n// 播放器逻辑\nconst player = usePlayer(emit)\n\n// 同步 canvas ref 到 player\nwatch(canvasRef, (el) => {\n player.canvasRef.value = el\n}, { immediate: true })\n\n// isLive 计算属性(用于模板)\nconst isLive = computed(() => player.isLive.value)\n\n// 控制条逻辑\nconst isPlayingRef = computed(() => player.state.isPlaying)\nconst isLoadedRef = computed(() => player.state.isLoaded)\nconst currentTimeRef = computed(() => player.state.currentTime)\nconst durationRef = computed(() => player.state.duration)\nconst segmentIndexRef = computed(() => player.state.segmentIndex)\nconst fetchedSegmentCountRef = computed(() => player.state.fetchedSegmentCount)\nconst totalSegmentsRef = computed(() => player.state.totalSegments)\n\nconst controls = useControls({\n isPlaying: isPlayingRef,\n isLoaded: isLoadedRef,\n currentTime: currentTimeRef,\n duration: durationRef,\n segmentIndex: segmentIndexRef,\n fetchedSegmentCount: fetchedSegmentCountRef,\n totalSegments: totalSegmentsRef,\n isLive,\n seek: player.seek\n})\n\n// 视频包装器引用\nconst videoWrapperRef = ref<HTMLElement | null>(null)\n\n// 解析 src\nfunction parseSrc(src: EcPlayerSource | undefined): { url: string; isLive: boolean } | null {\n if (!src || !src.url) return null\n return { url: src.url, isLive: src.isLive ?? false }\n}\n\n// 切换播放/暂停\nfunction togglePlayback(): void {\n if (player.state.isPlaying) {\n player.pause()\n } else {\n player.resume()\n }\n}\n\n// 切换全屏\nfunction toggleFullscreen(): void {\n if (!containerRef.value) return\n\n if (!isFullscreen.value) {\n if (containerRef.value.requestFullscreen) {\n containerRef.value.requestFullscreen()\n }\n } else {\n if (document.exitFullscreen) {\n document.exitFullscreen()\n }\n }\n}\n\n// 监听全屏变化\nfunction handleFullscreenChange(): void {\n isFullscreen.value = !!document.fullscreenElement\n}\n\n// 暴露的 play 方法(简化版)\nfunction play(): void {\n const parsed = parseSrc(props.src)\n if (parsed) {\n player.play(parsed)\n }\n}\n\n// 监听 src 变化,自动播放\nwatch(() => props.src, (newSrc) => {\n const parsed = parseSrc(newSrc)\n if (parsed && parsed.url) {\n player.play(parsed)\n }\n}, { immediate: true, deep: true })\n\n// 挂载时监听全屏事件\nonMounted(() => {\n document.addEventListener('fullscreenchange', handleFullscreenChange)\n})\n\n// 卸载时移除监听\nonUnmounted(() => {\n document.removeEventListener('fullscreenchange', handleFullscreenChange)\n})\n\n// Expose 方法\ndefineExpose<EcPlayerExpose>({\n play,\n pause: player.pause,\n seek: player.seek\n})\n</script>\n\n<style scoped>\n.gt-player-container {\n width: 100%;\n height: 100%;\n}\n\n.gt-video-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n background: #000;\n overflow: hidden;\n}\n\n.gt-player-canvas {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.gt-placeholder {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #000;\n}\n\n/* 加载中动画 */\n.gt-loading-spinner {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.5);\n z-index: 10;\n}\n\n.gt-spinner {\n width: 8vmin;\n height: 8vmin;\n border: 0.5vmin solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: gt-spin 1s linear infinite;\n}\n\n@keyframes gt-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* 视频控制条 */\n.gt-video-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 10px 16px;\n background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));\n opacity: 0;\n transition: opacity 0.3s ease;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.gt-flex-spacer {\n flex: 1;\n}\n\n.gt-video-controls.visible {\n opacity: 1;\n}\n\n.gt-control-btn {\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n\n.gt-control-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n.gt-control-btn svg {\n width: 14px;\n height: 14px;\n}\n\n.gt-progress-container {\n flex: 1;\n position: relative;\n cursor: pointer;\n height: 20px;\n display: flex;\n align-items: center;\n}\n\n.gt-progress-bar {\n width: 100%;\n height: 4px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 2px;\n position: relative;\n overflow: hidden;\n transition: height 0.2s ease;\n}\n\n.gt-progress-container:hover .gt-progress-bar {\n height: 6px;\n}\n\n.gt-progress-buffered {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: rgba(255, 255, 255, 0.4);\n border-radius: 2px;\n transition: width 0.1s;\n}\n\n.gt-progress-played {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: #ff2d55;\n border-radius: 2px;\n transition: width 0.1s;\n}\n\n.gt-progress-handle {\n position: absolute;\n right: -6px;\n top: 50%;\n transform: translateY(-50%);\n width: 14px;\n height: 14px;\n background: #ff2d55;\n border-radius: 50%;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n opacity: 0;\n transition: opacity 0.2s, transform 0.2s;\n}\n\n.gt-progress-container:hover .gt-progress-handle {\n opacity: 1;\n transform: translateY(-50%) scale(1.2);\n}\n\n.gt-progress-tooltip {\n position: absolute;\n bottom: calc(100% + 8px);\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: white;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 11px;\n white-space: nowrap;\n pointer-events: none;\n}\n\n.gt-time-display {\n color: white;\n font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n.gt-time-separator {\n margin: 0 4px;\n opacity: 0.6;\n}\n\n/* 直播指示器 */\n.gt-live-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n color: white;\n font-size: 12px;\n font-weight: 500;\n}\n\n.gt-live-dot {\n width: 8px;\n height: 8px;\n background: #ff2d55;\n border-radius: 50%;\n animation: gt-live-pulse 1.5s ease-in-out infinite;\n}\n\n.gt-live-text {\n color: #ff2d55;\n}\n\n@keyframes gt-live-pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.4;\n }\n}\n\n/* 居中播放按钮 */\n.gt-center-play-btn {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 56px;\n height: 56px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.gt-center-play-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n transform: translate(-50%, -50%) scale(1.05);\n}\n\n.gt-center-play-btn svg {\n width: 24px;\n height: 24px;\n color: white;\n margin-left: 3px;\n}\n\n/* 环境信息面板 */\n.gt-env-panel {\n position: absolute;\n top: 12px;\n left: 12px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 6px;\n padding: 8px 10px;\n font-size: 11px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n min-width: 160px;\n max-width: 200px;\n max-height: calc(100% - 24px);\n overflow-y: auto;\n backdrop-filter: blur(10px);\n animation: gt-env-fade-in 0.15s ease;\n z-index: 15;\n}\n\n.gt-env-panel::-webkit-scrollbar {\n width: 4px;\n}\n\n.gt-env-panel::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.gt-env-panel::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.3);\n border-radius: 2px;\n}\n\n@keyframes gt-env-fade-in {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.gt-env-panel .gt-env-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 2px 0;\n color: rgba(255, 255, 255, 0.7);\n}\n\n.gt-env-panel .gt-env-row span:first-child {\n color: rgba(255, 255, 255, 0.5);\n margin-right: 16px;\n}\n\n.gt-env-panel .gt-env-row span:last-child {\n font-variant-numeric: tabular-nums;\n}\n\n.gt-env-panel .gt-env-row .ok { color: #4caf50; }\n.gt-env-panel .gt-env-row .warn { color: #ff9800; }\n.gt-env-panel .gt-env-row .err { color: #f44336; }\n\n.gt-env-divider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 4px 0;\n}\n</style>\n"],"names":["globalConfig","setLogLevel","coreSetLogLevel","_createElementBlock","_createElementVNode","_unref","_openBlock","_renderSlot","_normalizeClass","_Fragment","_toDisplayString","_normalizeStyle"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/types.ts","../src/composables/usePlayer.ts","../src/composables/useControls.ts","../src/composables/useEnvInfo.ts","../src/components/EcPlayer.vue"],"sourcesContent":["/**\n * Vue 组件类型定义\n */\n\nimport type { EcPlayerCoreConfig, EcPlayerState, EnvInfo } from '@give-tech/ec-player'\nexport { LogLevel } from '@give-tech/ec-player'\n\n/**\n * WASM 路径配置\n */\nexport interface WasmPathConfig {\n /**\n * SIMD 版本 WASM 路径(推荐,性能更好)\n * 用于支持 SIMD 的现代浏览器\n */\n simd?: string\n /**\n * 非 SIMD 版本 WASM 路径\n * 用于不支持 SIMD 的旧浏览器作为降级方案\n */\n nonSimd?: string\n}\n\n/**\n * 全局配置\n *\n * 必须在使用 EcPlayer 组件前调用\n *\n * @example\n * ```typescript\n * // main.ts\n * import { configureEcPlayer } from '@give-tech/ec-player-vue'\n *\n * // 配置 SIMD 和非 SIMD 路径,支持自动降级\n * configureEcPlayer({\n * wasm: {\n * simd: '/wasm/decoder-simd.js',\n * nonSimd: '/wasm/decoder.js'\n * }\n * })\n *\n * // 使用 CDN\n * configureEcPlayer({\n * wasm: {\n * simd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder-simd.js',\n * nonSimd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder.js'\n * }\n * })\n * ```\n */\nexport interface EcPlayerGlobalConfig {\n /**\n * WASM 路径配置(必填)\n * 支持 SIMD 和非 SIMD 自动降级\n */\n wasm?: WasmPathConfig\n /** 默认是否显示控制条 */\n showControls?: boolean\n /** 默认缓冲目标帧数 */\n targetBufferSize?: number\n /** 默认每批解码帧数 */\n decodeBatchSize?: number\n /** 默认队列上限 */\n maxQueueSize?: number\n}\n\n/**\n * 播放器配置选项\n */\nexport interface PlayerOptions {\n /** 帧缓冲区目标大小 */\n targetBufferSize?: number\n /** 每批解码帧数 */\n decodeBatchSize?: number\n /** NAL/Sample 队列上限 */\n maxQueueSize?: number\n}\n\n/**\n * 视频源配置\n */\nexport interface EcPlayerSource {\n /** 视频 URL */\n url: string\n /** 是否为直播流 */\n isLive?: boolean\n /** 版本号,用于强制重新加载 */\n version?: number\n}\n\n/**\n * 控制条选项\n */\nexport interface EcPlayerControlOption {\n /** 是否显示控制条 */\n show?: boolean\n /** 是否显示全屏按钮 */\n fullscreen?: boolean\n /** 是否显示环境信息按钮 */\n info?: boolean\n}\n\n/**\n * EcPlayer 组件 Props\n */\nexport interface EcPlayerProps {\n /** 视频源 */\n src?: EcPlayerSource\n /** 控制条选项 */\n controlOption?: EcPlayerControlOption\n}\n\n/**\n * EcPlayer 组件 Emits\n */\nexport interface EcPlayerEmits {\n /** 播放时触发 */\n (e: 'play'): void\n /** 暂停时触发 */\n (e: 'pause'): void\n}\n\n/**\n * EcPlayer 组件 Expose 方法\n */\nexport interface EcPlayerExpose {\n /** 播放 */\n play: () => void\n /** 暂停 */\n pause: () => void\n /** 跳转到指定时间(毫秒) */\n seek: (time: number) => Promise<void>\n}\n\n/**\n * 环境信息面板数据\n */\nexport interface EnvPanelData {\n envInfo: EnvInfo | null\n state: Partial<EcPlayerState>\n}\n\n/**\n * 播放器配置(从 Props 转换)\n */\nexport interface PlayerConfigFromProps extends EcPlayerCoreConfig {\n wasmPath: string\n targetBufferSize: number\n decodeBatchSize: number\n maxQueueSize: number\n}\n\n/**\n * 默认 Props 值\n */\nexport const DEFAULT_EC_PLAYER_PROPS = {\n src: undefined as EcPlayerSource | undefined,\n controlOption: {\n show: true,\n fullscreen: true,\n info: true\n }\n}\n\n/**\n * 默认播放器配置\n */\nexport const DEFAULT_PLAYER_OPTIONS: Required<PlayerOptions> = {\n targetBufferSize: 60,\n decodeBatchSize: 2,\n maxQueueSize: 200\n}\n\n/**\n * 默认 WASM 配置\n */\nexport const DEFAULT_WASM_CONFIG: WasmPathConfig = {\n simd: '/wasm/decoder-simd.js',\n nonSimd: '/wasm/decoder.js'\n}\n\n/**\n * 全局配置存储\n */\nlet globalConfig: EcPlayerGlobalConfig = {}\n\n/**\n * 配置 EcPlayer 全局默认值\n *\n * @example\n * ```typescript\n * // main.ts\n * import { configureEcPlayer } from '@give-tech/ec-player-vue'\n *\n * // 配置 SIMD 和非 SIMD 路径,支持自动降级\n * configureEcPlayer({\n * wasm: {\n * simd: '/wasm/decoder-simd.js',\n * nonSimd: '/wasm/decoder.js'\n * }\n * })\n *\n * // 使用 CDN\n * configureEcPlayer({\n * wasm: {\n * simd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder-simd.js',\n * nonSimd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder.js'\n * }\n * })\n * ```\n */\nexport function configureEcPlayer(config: EcPlayerGlobalConfig): void {\n globalConfig = { ...globalConfig, ...config }\n}\n\n/**\n * 获取全局配置\n */\nexport function getGlobalConfig(): EcPlayerGlobalConfig {\n return { ...globalConfig }\n}\n\n/**\n * 获取 WASM 路径配置\n */\nexport function getWasmConfig(): WasmPathConfig {\n return {\n simd: globalConfig.wasm?.simd ?? DEFAULT_WASM_CONFIG.simd,\n nonSimd: globalConfig.wasm?.nonSimd ?? DEFAULT_WASM_CONFIG.nonSimd\n }\n}\n\n/**\n * 获取合并后的配置(props > 全局配置 > 默认值)\n */\nexport function getMergedConfig(props: EcPlayerProps) {\n return {\n src: props.src ?? DEFAULT_EC_PLAYER_PROPS.src,\n controlOption: {\n show: props.controlOption?.show ?? globalConfig.showControls ?? DEFAULT_EC_PLAYER_PROPS.controlOption.show,\n fullscreen: props.controlOption?.fullscreen ?? DEFAULT_EC_PLAYER_PROPS.controlOption.fullscreen,\n info: props.controlOption?.info ?? DEFAULT_EC_PLAYER_PROPS.controlOption.info\n }\n }\n}\n\n/**\n * 获取合并后的播放器配置(options > 全局配置 > 默认值)\n */\nexport function getMergedPlayerOptions(options: PlayerOptions): Required<PlayerOptions> {\n return {\n targetBufferSize: options.targetBufferSize ?? globalConfig.targetBufferSize ?? DEFAULT_PLAYER_OPTIONS.targetBufferSize,\n decodeBatchSize: options.decodeBatchSize ?? globalConfig.decodeBatchSize ?? DEFAULT_PLAYER_OPTIONS.decodeBatchSize,\n maxQueueSize: options.maxQueueSize ?? globalConfig.maxQueueSize ?? DEFAULT_PLAYER_OPTIONS.maxQueueSize\n }\n}\n\n/**\n * 根据环境能力选择合适的 WASM 路径\n *\n * @param hasSimdSupport 是否支持 SIMD\n * @returns 实际使用的 WASM 路径\n */\nexport function resolveWasmPath(hasSimdSupport: boolean): string {\n const wasmConfig = getWasmConfig()\n\n // 根据 SIMD 支持情况选择\n if (hasSimdSupport && wasmConfig.simd) {\n console.log('[EcPlayer] Using SIMD WASM:', wasmConfig.simd)\n return wasmConfig.simd\n }\n\n if (wasmConfig.nonSimd) {\n console.log('[EcPlayer] SIMD not supported, using non-SIMD WASM:', wasmConfig.nonSimd)\n return wasmConfig.nonSimd\n }\n\n // 降级到 SIMD 版本(兜底)\n console.log('[EcPlayer] No non-SIMD path configured, falling back to SIMD:', wasmConfig.simd)\n return wasmConfig.simd!\n}\n","/**\n * 播放器核心逻辑\n */\n\nimport { ref, shallowRef, reactive, onUnmounted, type Ref } from 'vue'\nimport {\n EcPlayerCore,\n EnvDetector,\n setLogLevel as coreSetLogLevel,\n type EcPlayerCoreConfig,\n type EcPlayerState,\n type EcPlayerCallbacks\n} from '@give-tech/ec-player'\nimport type { EcPlayerProps, PlayerOptions } from '../types'\nimport { resolveWasmPath, DEFAULT_PLAYER_OPTIONS, getGlobalConfig } from '../types'\n\n/**\n * 播放器逻辑返回值\n */\nexport interface UsePlayerReturn {\n /** Canvas 元素引用 */\n canvasRef: Ref<HTMLCanvasElement | null>\n /** 播放器实例 */\n player: Ref<EcPlayerCore | null>\n /** 响应式状态 */\n state: EcPlayerState\n /** 是否正在加载 */\n isLoading: Ref<boolean>\n /** 检测到的格式 */\n detectedFormat: Ref<string>\n /** 是否为直播流 */\n isLive: Ref<boolean>\n /** 播放 */\n play: (options: { url: string; isLive?: boolean }) => Promise<void>\n /** 设置配置 */\n setOptions: (options: PlayerOptions) => void\n /** 设置日志级别 */\n setLogLevel: (level: number) => void\n /** 暂停 */\n pause: () => void\n /** 恢复播放 */\n resume: () => Promise<void>\n /** 跳转 */\n seek: (time: number) => Promise<void>\n /** 获取播放器实例 */\n getPlayer: () => EcPlayerCore | null\n /** 获取状态 */\n getState: () => EcPlayerState\n /** 获取当前时间 */\n getCurrentTime: () => number\n /** 获取总时长 */\n getDuration: () => number\n /** 销毁播放器 */\n destroy: () => void\n}\n\n/**\n * 从配置选项创建播放器配置\n */\nfunction createConfig(options: Required<PlayerOptions>): Omit<EcPlayerCoreConfig, 'isLive'> {\n // 检测 SIMD 支持\n const envInfo = EnvDetector.detect()\n const wasmPath = resolveWasmPath(envInfo.capabilities.wasmSimd)\n\n return {\n wasmPath,\n targetBufferSize: options.targetBufferSize,\n decodeBatchSize: options.decodeBatchSize,\n maxQueueSize: options.maxQueueSize\n }\n}\n\n/**\n * 播放器逻辑 Hook\n */\nexport function usePlayer(\n emit: {\n (e: 'play'): void\n (e: 'pause'): void\n }\n): UsePlayerReturn {\n const canvasRef = ref<HTMLCanvasElement | null>(null)\n const player = shallowRef<EcPlayerCore | null>(null)\n const isLoading = ref(false)\n const detectedFormat = ref('')\n const isLive = ref(false) // 跟踪当前是否为直播流\n\n // 用于追踪最新的 play 请求,解决快速切换源时的竞态条件\n let playRequestId = 0\n\n // 当前播放器配置(初始化时合并全局配置和默认值)\n const globalConfig = getGlobalConfig()\n const currentOptions = reactive<Required<PlayerOptions>>({\n targetBufferSize: globalConfig.targetBufferSize ?? DEFAULT_PLAYER_OPTIONS.targetBufferSize,\n decodeBatchSize: globalConfig.decodeBatchSize ?? DEFAULT_PLAYER_OPTIONS.decodeBatchSize,\n maxQueueSize: globalConfig.maxQueueSize ?? DEFAULT_PLAYER_OPTIONS.maxQueueSize\n })\n\n // 响应式状态\n const state = reactive<EcPlayerState>({\n isPlaying: false,\n isLoaded: false,\n isLoading: false,\n fps: 0,\n resolution: '-',\n decoded: 0,\n downloaded: 0,\n droppedFrames: 0,\n isPrefetching: false,\n segmentIndex: 0,\n fetchedSegmentCount: 0,\n totalSegments: 0,\n downloadSpeed: 0,\n currentTime: 0,\n duration: 0\n })\n\n // 创建回调\n const callbacks: EcPlayerCallbacks = {\n onStateChange: (partial) => {\n Object.assign(state, partial)\n\n // 派发 play/pause 事件\n if (partial.isPlaying === true) {\n emit('play')\n } else if (partial.isPlaying === false && state.isPlaying) {\n emit('pause')\n }\n },\n onError: (error) => {\n console.error('[EcPlayer] Error:', error)\n }\n }\n\n // 播放(加载并播放)\n async function play(options: { url: string; isLive?: boolean }): Promise<void> {\n const { url, isLive: isLiveParam = false } = options\n if (!url) {\n console.error('[EcPlayer] URL is required')\n return\n }\n\n // 递增请求 ID,用于追踪这次调用\n const currentRequestId = ++playRequestId\n console.log('[EcPlayer] play() called, requestId:', currentRequestId)\n\n // 如果正在加载中,先销毁当前播放器\n if (isLoading.value && player.value) {\n console.log('[EcPlayer] Aborting current load to start new one...')\n player.value.destroy()\n player.value = null\n }\n\n // 等待 canvas 准备好\n let retries = 10\n while (!canvasRef.value && retries > 0) {\n await new Promise(resolve => setTimeout(resolve, 100))\n retries--\n }\n\n // 检查是否有更新的请求\n if (currentRequestId !== playRequestId) {\n console.log('[EcPlayer] Request obsolete after waiting for canvas, aborting')\n return\n }\n\n if (!canvasRef.value) {\n console.error('[EcPlayer] Canvas element not found')\n return\n }\n\n isLoading.value = true\n isLive.value = isLiveParam\n\n try {\n // 销毁旧播放器\n if (player.value) {\n player.value.destroy()\n player.value = null\n }\n\n // 重置状态\n state.isPlaying = false\n state.isLoaded = false\n state.fps = 0\n state.resolution = '-'\n state.decoded = 0\n state.downloaded = 0\n state.droppedFrames = 0\n state.segmentIndex = 0\n state.totalSegments = 0\n state.downloadSpeed = 0\n state.currentTime = 0\n state.duration = 0\n detectedFormat.value = ''\n\n // 创建配置\n const config: EcPlayerCoreConfig & { canvas: HTMLCanvasElement } = {\n ...createConfig(currentOptions),\n canvas: canvasRef.value,\n isLive: isLiveParam\n }\n\n // 创建播放器\n player.value = new EcPlayerCore(config, callbacks)\n\n // 加载视频\n await player.value.load(url, isLiveParam)\n\n // 检查是否有更新的请求\n if (currentRequestId !== playRequestId) {\n console.log('[EcPlayer] Request obsolete after load, aborting')\n // 销毁刚创建的播放器\n if (player.value) {\n player.value.destroy()\n player.value = null\n }\n return\n }\n\n // 获取检测到的格式\n detectedFormat.value = player.value.getDetectedFormat()\n\n // 标记加载完成\n state.isLoaded = true\n\n // 开始播放\n await player.value.play()\n\n console.log('[EcPlayer] Playing stream, format:', detectedFormat.value)\n } catch (error: any) {\n console.error('[EcPlayer] Failed to play:', error?.message || error)\n } finally {\n // 只有最新的请求才能重置 isLoading 状态\n if (currentRequestId === playRequestId) {\n isLoading.value = false\n console.log('[EcPlayer] play() finished, requestId:', currentRequestId)\n } else {\n console.log('[EcPlayer] play() finished but ignored (obsolete), requestId:', currentRequestId, 'current:', playRequestId)\n }\n }\n }\n\n // 设置配置\n function setOptions(options: PlayerOptions): void {\n if (options.targetBufferSize !== undefined) {\n currentOptions.targetBufferSize = options.targetBufferSize\n }\n if (options.decodeBatchSize !== undefined) {\n currentOptions.decodeBatchSize = options.decodeBatchSize\n }\n if (options.maxQueueSize !== undefined) {\n currentOptions.maxQueueSize = options.maxQueueSize\n }\n console.log('[EcPlayer] Options updated:', currentOptions)\n }\n\n // 设置日志级别\n function setLogLevel(level: number): void {\n coreSetLogLevel(level)\n }\n\n // 暂停\n function pause(): void {\n player.value?.pause()\n }\n\n // 恢复播放\n async function resume(): Promise<void> {\n if (!player.value) return\n await player.value.play()\n }\n\n // 跳转\n async function seek(time: number): Promise<void> {\n if (!player.value) return\n await player.value.seek(time)\n }\n\n // 获取播放器实例\n function getPlayer(): EcPlayerCore | null {\n return player.value\n }\n\n // 获取状态\n function getState(): EcPlayerState {\n return player.value?.getState() ?? { ...state }\n }\n\n // 获取当前时间\n function getCurrentTime(): number {\n return player.value?.getCurrentTime() ?? 0\n }\n\n // 获取总时长\n function getDuration(): number {\n return player.value?.getDuration() ?? 0\n }\n\n // 销毁播放器\n function destroy(): void {\n if (player.value) {\n player.value.destroy()\n player.value = null\n }\n }\n\n // 组件卸载时销毁\n onUnmounted(() => {\n destroy()\n })\n\n return {\n canvasRef,\n player,\n state,\n isLoading,\n detectedFormat,\n isLive,\n play,\n setOptions,\n setLogLevel,\n pause,\n resume,\n seek,\n getPlayer,\n getState,\n getCurrentTime,\n getDuration,\n destroy\n }\n}\n","/**\n * 播放器控制条逻辑\n */\n\nimport { ref, computed, type Ref } from 'vue'\n\n/**\n * 控制条逻辑参数\n */\nexport interface UseControlsParams {\n /** 是否正在播放 */\n isPlaying: Ref<boolean>\n /** 是否已加载 */\n isLoaded: Ref<boolean>\n /** 当前时间(毫秒) */\n currentTime: Ref<number>\n /** 总时长(毫秒) */\n duration: Ref<number>\n /** 已解析的分片索引 */\n segmentIndex: Ref<number>\n /** 已下载的分片总数 */\n fetchedSegmentCount: Ref<number>\n /** 总分片数 */\n totalSegments: Ref<number>\n /** 是否为直播流 */\n isLive: Ref<boolean>\n /** 跳转方法 */\n seek: (time: number) => Promise<void>\n}\n\n/**\n * 控制条逻辑返回值\n */\nexport interface UseControlsReturn {\n /** 是否显示控制条 */\n showControls: Ref<boolean>\n /** 进度条 hover 时间 */\n hoverTime: Ref<number | null>\n /** 进度条 hover 位置 */\n hoverPosition: Ref<number>\n /** 播放进度百分比 */\n playProgress: Ref<number>\n /** 缓冲进度百分比 */\n bufferProgress: Ref<number>\n /** 显示控制条 */\n showControlsBar: () => void\n /** 隐藏控制条 */\n hideControlsBar: () => void\n /** 处理进度条点击 */\n handleProgressClick: (event: MouseEvent) => Promise<void>\n /** 处理进度条悬停 */\n handleProgressHover: (event: MouseEvent) => void\n /** 格式化时间 */\n formatTime: (ms: number) => string\n}\n\n/**\n * 控制条逻辑 Hook\n */\nexport function useControls(params: UseControlsParams): UseControlsReturn {\n let hideControlsTimer: number | null = null\n const {\n isPlaying,\n isLoaded,\n currentTime,\n duration,\n segmentIndex,\n fetchedSegmentCount,\n totalSegments,\n isLive,\n seek\n } = params\n\n const showControls = ref(true)\n const hoverTime = ref<number | null>(null)\n const hoverPosition = ref(0)\n\n // 计算播放进度\n const playProgress = computed(() => {\n if (duration.value === 0) return 0\n return (currentTime.value / duration.value) * 100\n })\n\n // 计算缓冲进度(已下载的分段数)\n const bufferProgress = computed(() => {\n if (totalSegments.value === 0) return 0\n return (fetchedSegmentCount.value / totalSegments.value) * 100\n })\n\n // 显示控制条\n function showControlsBar(): void {\n showControls.value = true\n if (hideControlsTimer) {\n clearTimeout(hideControlsTimer)\n }\n hideControlsTimer = window.setTimeout(() => {\n if (isPlaying.value) {\n showControls.value = false\n }\n }, 3000)\n }\n\n // 隐藏控制条\n function hideControlsBar(): void {\n if (isPlaying.value) {\n showControls.value = false\n }\n }\n\n // 处理进度条点击\n async function handleProgressClick(event: MouseEvent): Promise<void> {\n if (duration.value === 0) return\n\n const target = event.currentTarget as HTMLElement\n const rect = target.getBoundingClientRect()\n const clickX = event.clientX - rect.left\n const percentage = clickX / rect.width\n const targetTime = percentage * duration.value\n\n await seek(targetTime)\n }\n\n // 处理进度条悬停\n function handleProgressHover(event: MouseEvent): void {\n if (duration.value === 0) return\n\n const target = event.currentTarget as HTMLElement\n const rect = target.getBoundingClientRect()\n const hoverX = event.clientX - rect.left\n const percentage = Math.max(0, Math.min(1, hoverX / rect.width))\n\n hoverTime.value = percentage * duration.value\n hoverPosition.value = percentage * 100\n }\n\n // 格式化时间\n function formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000)\n const hours = Math.floor(totalSeconds / 3600)\n const minutes = Math.floor((totalSeconds % 3600) / 60)\n const seconds = totalSeconds % 60\n\n if (hours > 0) {\n return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`\n }\n return `${minutes}:${seconds.toString().padStart(2, '0')}`\n }\n\n return {\n showControls,\n hoverTime,\n hoverPosition,\n playProgress,\n bufferProgress,\n showControlsBar,\n hideControlsBar,\n handleProgressClick,\n handleProgressHover,\n formatTime\n }\n}\n","/**\n * 环境信息逻辑\n */\n\nimport { ref, computed, onMounted, type Ref } from 'vue'\nimport { EnvDetector, type EnvInfo } from '@give-tech/ec-player'\n\n/**\n * 环境信息逻辑返回值\n */\nexport interface UseEnvInfoReturn {\n /** 环境信息 */\n envInfo: Ref<EnvInfo | null>\n /** 是否显示环境面板 */\n showEnvPanel: Ref<boolean>\n /** 环境类型文本 */\n envTypeText: Ref<string>\n /** 切换环境面板显示 */\n toggleEnvPanel: () => void\n /** 检测环境 */\n detectEnv: () => void\n}\n\n/**\n * 环境信息逻辑 Hook\n */\nexport function useEnvInfo(\n initialShowPanel: boolean = false\n): UseEnvInfoReturn {\n const envInfo = ref<EnvInfo | null>(null)\n const showEnvPanel = ref(initialShowPanel)\n\n // 环境类型文本\n const envTypeText = computed(() => {\n if (!envInfo.value) return '未知'\n const typeMap: Record<string, string> = {\n 'pc-browser': 'PC 浏览器',\n 'mobile-browser': '移动端浏览器',\n 'wechat-miniprogram': '微信小程序',\n 'alipay-miniprogram': '支付宝小程序',\n 'other-miniprogram': '其他小程序',\n 'unknown': '未知',\n }\n return typeMap[envInfo.value.platform] || envInfo.value.platform\n })\n\n // 切换环境面板显示\n function toggleEnvPanel(): void {\n showEnvPanel.value = !showEnvPanel.value\n }\n\n // 检测环境\n function detectEnv(): void {\n envInfo.value = EnvDetector.detect()\n console.log('[EcPlayer] 环境检测结果:', envInfo.value)\n }\n\n // 组件挂载时检测环境\n onMounted(() => {\n detectEnv()\n })\n\n return {\n envInfo,\n showEnvPanel,\n envTypeText,\n toggleEnvPanel,\n detectEnv\n }\n}\n","<template>\n <div class=\"gt-player-container\" ref=\"containerRef\">\n <div\n class=\"gt-video-wrapper\"\n ref=\"videoWrapperRef\"\n @mouseenter=\"controls.showControlsBar\"\n @mousemove=\"controls.showControlsBar\"\n @mouseleave=\"controls.hideControlsBar\"\n >\n <!-- Canvas 视频渲染 -->\n <canvas ref=\"canvasRef\" class=\"gt-player-canvas\"></canvas>\n\n <!-- 封面/占位内容(未加载时显示) -->\n <div v-if=\"!player.state.isLoaded\" class=\"gt-placeholder\">\n <slot name=\"placeholder\"></slot>\n </div>\n\n <!-- 环境信息面板 -->\n <div v-if=\"showEnvPanel && envInfo\" class=\"gt-env-panel\">\n <div class=\"gt-env-row\">\n <span>platform</span>\n <span :class=\"envInfo.isMiniProgram ? 'warn' : ''\">{{ envTypeText }}</span>\n </div>\n <div class=\"gt-env-divider\"></div>\n <div class=\"gt-env-row\">\n <span>SIMD</span>\n <span :class=\"envInfo.capabilities.wasmSimd ? 'ok' : 'err'\">\n {{ envInfo.capabilities.wasmSimd ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>WebGL</span>\n <span :class=\"envInfo.capabilities.webGL ? 'ok' : 'err'\">\n {{ envInfo.capabilities.webGL ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>WebGL2</span>\n <span :class=\"envInfo.capabilities.webGL2 ? 'ok' : 'err'\">\n {{ envInfo.capabilities.webGL2 ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>SAB</span>\n <span :class=\"envInfo.capabilities.sharedArrayBuffer ? 'ok' : 'err'\">\n {{ envInfo.capabilities.sharedArrayBuffer ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>Threads</span>\n <span :class=\"envInfo.capabilities.wasmThreads ? 'ok' : 'err'\">\n {{ envInfo.capabilities.wasmThreads ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>WebAudio</span>\n <span :class=\"envInfo.capabilities.webAudio ? 'ok' : 'err'\">\n {{ envInfo.capabilities.webAudio ? 'Yes' : 'No' }}\n </span>\n </div>\n <div class=\"gt-env-row\">\n <span>Stream</span>\n <span :class=\"envInfo.capabilities.readableStream ? 'ok' : 'err'\">\n {{ envInfo.capabilities.readableStream ? 'Yes' : 'No' }}\n </span>\n </div>\n <template v-if=\"player.state.isLoaded\">\n <div class=\"gt-env-divider\"></div>\n <div class=\"gt-env-row\" v-if=\"player.detectedFormat.value\"><span>Format</span><span>{{ player.detectedFormat.value }}</span></div>\n <div class=\"gt-env-row\"><span>FPS</span><span>{{ player.state.fps }}</span></div>\n <div class=\"gt-env-row\"><span>Resolution</span><span>{{ player.state.resolution }}</span></div>\n <div class=\"gt-env-row\"><span>downloaded</span><span>{{ player.state.downloaded }}</span></div>\n <div class=\"gt-env-row\"><span>decoded</span><span>{{ player.state.decoded }}</span></div>\n <div class=\"gt-env-row\"><span>Dropped</span><span>{{ player.state.droppedFrames }}</span></div>\n <div class=\"gt-env-row\"><span>Speed</span><span>{{ player.state.downloadSpeed || 0 }} KB/s</span></div>\n </template>\n </div>\n\n <!-- 加载中动画 -->\n <div class=\"gt-loading-spinner\" v-if=\"player.isLoading.value\">\n <slot name=\"loading\">\n <div class=\"gt-spinner\"></div>\n </slot>\n </div>\n\n <!-- 视频控制条 -->\n <div\n v-if=\"controlOption.show\"\n class=\"gt-video-controls\"\n :class=\"{ visible: controls.showControls.value || !player.state.isPlaying }\"\n >\n <button class=\"gt-control-btn\" @click=\"togglePlayback\">\n <svg v-if=\"player.state.isPlaying\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n <rect x=\"14\" y=\"4\" width=\"4\" height=\"16\" rx=\"1\"/>\n </svg>\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n </button>\n\n <!-- 直播标识 -->\n <span class=\"gt-live-indicator\" v-if=\"isLive\">\n <span class=\"gt-live-dot\"></span>\n <span class=\"gt-live-text\">实时</span>\n </span>\n\n <!-- 点播时间显示 -->\n <span class=\"gt-time-display\" v-else>\n <span class=\"gt-time-current\">{{ controls.formatTime(player.state.currentTime) }}</span>\n <span class=\"gt-time-separator\">/</span>\n <span class=\"gt-time-duration\">{{ controls.formatTime(player.state.duration) }}</span>\n </span>\n\n <!-- 进度条 (仅点播) -->\n <div\n v-if=\"!isLive\"\n class=\"gt-progress-container\"\n @click=\"controls.handleProgressClick\"\n @mousemove=\"controls.handleProgressHover\"\n @mouseleave=\"controls.hoverTime.value = null\"\n >\n <div class=\"gt-progress-bar\">\n <div class=\"gt-progress-buffered\" :style=\"{ width: controls.bufferProgress.value + '%' }\"></div>\n <div class=\"gt-progress-played\" :style=\"{ width: controls.playProgress.value + '%' }\">\n <div class=\"gt-progress-handle\"></div>\n </div>\n </div>\n <div\n class=\"gt-progress-tooltip\"\n v-if=\"controls.hoverTime.value !== null\"\n :style=\"{ left: controls.hoverPosition.value + '%' }\"\n >\n {{ controls.formatTime(controls.hoverTime.value) }}\n </div>\n </div>\n\n <!-- 直播流占位符 -->\n <div class=\"gt-flex-spacer\" v-else></div>\n\n <!-- 环境信息按钮 -->\n <button v-if=\"controlOption.info\" class=\"gt-control-btn gt-env-info-btn\" @click=\"toggleEnvPanel\" :title=\"showEnvPanel ? '隐藏信息' : '显示信息'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/>\n </svg>\n </button>\n\n <!-- 全屏按钮 -->\n <button v-if=\"controlOption.fullscreen\" class=\"gt-control-btn gt-fullscreen-btn\" @click=\"toggleFullscreen\" :title=\"isFullscreen ? '退出全屏' : '全屏'\">\n <!-- 退出全屏图标 -->\n <svg v-if=\"isFullscreen\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\"/>\n </svg>\n <!-- 全屏图标 -->\n <svg v-else viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\"/>\n </svg>\n </button>\n </div>\n\n <!-- 大播放按钮(居中) -->\n <div\n class=\"gt-center-play-btn\"\n v-if=\"player.state.isLoaded && !player.state.isPlaying\"\n @click=\"togglePlayback\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M8 5v14l11-7z\"/>\n </svg>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch, onMounted, onUnmounted } from 'vue'\nimport { usePlayer } from '../composables/usePlayer'\nimport { useControls } from '../composables/useControls'\nimport { useEnvInfo } from '../composables/useEnvInfo'\nimport type { EcPlayerProps, EcPlayerExpose, EcPlayerSource, EcPlayerControlOption } from '../types'\nimport { DEFAULT_EC_PLAYER_PROPS } from '../types'\n\n// Props\nconst props = withDefaults(defineProps<EcPlayerProps>(), DEFAULT_EC_PLAYER_PROPS)\n\n// Emits\nconst emit = defineEmits<{\n (e: 'play'): void\n (e: 'pause'): void\n}>()\n\n// Canvas ref(本地)\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\n\n// 容器引用(用于全屏)\nconst containerRef = ref<HTMLElement | null>(null)\n\n// 全屏状态\nconst isFullscreen = ref(false)\n\n// 解析 controlOption\nconst controlOption = computed<Required<EcPlayerControlOption>>(() => ({\n show: props.controlOption?.show ?? DEFAULT_EC_PLAYER_PROPS.controlOption.show,\n fullscreen: props.controlOption?.fullscreen ?? DEFAULT_EC_PLAYER_PROPS.controlOption.fullscreen,\n info: props.controlOption?.info ?? DEFAULT_EC_PLAYER_PROPS.controlOption.info\n}))\n\n// 环境信息逻辑\nconst { envInfo, showEnvPanel, envTypeText, toggleEnvPanel } = useEnvInfo(false)\n\n// 播放器逻辑\nconst player = usePlayer(emit)\n\n// 同步 canvas ref 到 player\nwatch(canvasRef, (el) => {\n player.canvasRef.value = el\n}, { immediate: true })\n\n// isLive 计算属性(用于模板)\nconst isLive = computed(() => player.isLive.value)\n\n// 控制条逻辑\nconst isPlayingRef = computed(() => player.state.isPlaying)\nconst isLoadedRef = computed(() => player.state.isLoaded)\nconst currentTimeRef = computed(() => player.state.currentTime)\nconst durationRef = computed(() => player.state.duration)\nconst segmentIndexRef = computed(() => player.state.segmentIndex)\nconst fetchedSegmentCountRef = computed(() => player.state.fetchedSegmentCount)\nconst totalSegmentsRef = computed(() => player.state.totalSegments)\n\nconst controls = useControls({\n isPlaying: isPlayingRef,\n isLoaded: isLoadedRef,\n currentTime: currentTimeRef,\n duration: durationRef,\n segmentIndex: segmentIndexRef,\n fetchedSegmentCount: fetchedSegmentCountRef,\n totalSegments: totalSegmentsRef,\n isLive,\n seek: player.seek\n})\n\n// 视频包装器引用\nconst videoWrapperRef = ref<HTMLElement | null>(null)\n\n// 解析 src\nfunction parseSrc(src: EcPlayerSource | undefined): { url: string; isLive: boolean } | null {\n if (!src || !src.url) return null\n return { url: src.url, isLive: src.isLive ?? false }\n}\n\n// 切换播放/暂停\nfunction togglePlayback(): void {\n if (player.state.isPlaying) {\n player.pause()\n } else {\n player.resume()\n }\n}\n\n// 切换全屏\nfunction toggleFullscreen(): void {\n if (!containerRef.value) return\n\n if (!isFullscreen.value) {\n if (containerRef.value.requestFullscreen) {\n containerRef.value.requestFullscreen()\n }\n } else {\n if (document.exitFullscreen) {\n document.exitFullscreen()\n }\n }\n}\n\n// 监听全屏变化\nfunction handleFullscreenChange(): void {\n isFullscreen.value = !!document.fullscreenElement\n}\n\n// 暴露的 play 方法(简化版)\nfunction play(): void {\n const parsed = parseSrc(props.src)\n if (parsed) {\n player.play(parsed)\n }\n}\n\n// 监听 src 变化,自动播放\nwatch(() => props.src, (newSrc) => {\n const parsed = parseSrc(newSrc)\n if (parsed && parsed.url) {\n player.play(parsed)\n }\n}, { immediate: true, deep: true })\n\n// 挂载时监听全屏事件\nonMounted(() => {\n document.addEventListener('fullscreenchange', handleFullscreenChange)\n})\n\n// 卸载时移除监听\nonUnmounted(() => {\n document.removeEventListener('fullscreenchange', handleFullscreenChange)\n})\n\n// Expose 方法\ndefineExpose<EcPlayerExpose>({\n play,\n pause: player.pause,\n seek: player.seek\n})\n</script>\n\n<style scoped>\n.gt-player-container {\n width: 100%;\n height: 100%;\n}\n\n.gt-video-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n background: #000;\n overflow: hidden;\n}\n\n.gt-player-canvas {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.gt-placeholder {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #000;\n}\n\n/* 加载中动画 */\n.gt-loading-spinner {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(0, 0, 0, 0.5);\n z-index: 10;\n}\n\n.gt-spinner {\n width: 10%;\n max-width: 48px;\n min-width: 20px;\n aspect-ratio: 1/1;\n border: 0.3em solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: gt-spin 1s linear infinite;\n}\n\n@keyframes gt-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n/* 视频控制条 */\n.gt-video-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 10px 16px;\n background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));\n opacity: 0;\n transition: opacity 0.3s ease;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.gt-flex-spacer {\n flex: 1;\n}\n\n.gt-video-controls.visible {\n opacity: 1;\n}\n\n.gt-control-btn {\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n\n.gt-control-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n}\n\n.gt-control-btn svg {\n width: 14px;\n height: 14px;\n}\n\n.gt-progress-container {\n flex: 1;\n position: relative;\n cursor: pointer;\n height: 20px;\n display: flex;\n align-items: center;\n}\n\n.gt-progress-bar {\n width: 100%;\n height: 4px;\n background: rgba(255, 255, 255, 0.2);\n border-radius: 2px;\n position: relative;\n overflow: hidden;\n transition: height 0.2s ease;\n}\n\n.gt-progress-container:hover .gt-progress-bar {\n height: 6px;\n}\n\n.gt-progress-buffered {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: rgba(255, 255, 255, 0.4);\n border-radius: 2px;\n transition: width 0.1s;\n}\n\n.gt-progress-played {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: #ff2d55;\n border-radius: 2px;\n transition: width 0.1s;\n}\n\n.gt-progress-handle {\n position: absolute;\n right: -6px;\n top: 50%;\n transform: translateY(-50%);\n width: 14px;\n height: 14px;\n background: #ff2d55;\n border-radius: 50%;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n opacity: 0;\n transition: opacity 0.2s, transform 0.2s;\n}\n\n.gt-progress-container:hover .gt-progress-handle {\n opacity: 1;\n transform: translateY(-50%) scale(1.2);\n}\n\n.gt-progress-tooltip {\n position: absolute;\n bottom: calc(100% + 8px);\n transform: translateX(-50%);\n background: rgba(0, 0, 0, 0.85);\n color: white;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 11px;\n white-space: nowrap;\n pointer-events: none;\n}\n\n.gt-time-display {\n color: white;\n font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n.gt-time-separator {\n margin: 0 4px;\n opacity: 0.6;\n}\n\n/* 直播指示器 */\n.gt-live-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n color: white;\n font-size: 12px;\n font-weight: 500;\n}\n\n.gt-live-dot {\n width: 8px;\n height: 8px;\n background: #ff2d55;\n border-radius: 50%;\n animation: gt-live-pulse 1.5s ease-in-out infinite;\n}\n\n.gt-live-text {\n color: #ff2d55;\n}\n\n@keyframes gt-live-pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.4;\n }\n}\n\n/* 居中播放按钮 */\n.gt-center-play-btn {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 56px;\n height: 56px;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.gt-center-play-btn:hover {\n background: rgba(0, 0, 0, 0.7);\n transform: translate(-50%, -50%) scale(1.05);\n}\n\n.gt-center-play-btn svg {\n width: 24px;\n height: 24px;\n color: white;\n margin-left: 3px;\n}\n\n/* 环境信息面板 */\n.gt-env-panel {\n position: absolute;\n top: 12px;\n left: 12px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 6px;\n padding: 8px 10px;\n font-size: 11px;\n font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;\n min-width: 160px;\n max-width: 200px;\n max-height: calc(100% - 24px);\n overflow-y: auto;\n backdrop-filter: blur(10px);\n animation: gt-env-fade-in 0.15s ease;\n z-index: 15;\n}\n\n.gt-env-panel::-webkit-scrollbar {\n width: 4px;\n}\n\n.gt-env-panel::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.gt-env-panel::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.3);\n border-radius: 2px;\n}\n\n@keyframes gt-env-fade-in {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.gt-env-panel .gt-env-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 2px 0;\n color: rgba(255, 255, 255, 0.7);\n}\n\n.gt-env-panel .gt-env-row span:first-child {\n color: rgba(255, 255, 255, 0.5);\n margin-right: 16px;\n}\n\n.gt-env-panel .gt-env-row span:last-child {\n font-variant-numeric: tabular-nums;\n}\n\n.gt-env-panel .gt-env-row .ok { color: #4caf50; }\n.gt-env-panel .gt-env-row .warn { color: #ff9800; }\n.gt-env-panel .gt-env-row .err { color: #f44336; }\n\n.gt-env-divider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 4px 0;\n}\n</style>\n"],"names":["globalConfig","setLogLevel","coreSetLogLevel","_createElementBlock","_createElementVNode","_unref","_openBlock","_renderSlot","_normalizeClass","_Fragment","_toDisplayString","_normalizeStyle"],"mappings":"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"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Vue 组件类型定义
3
+ */
4
+ import type { EcPlayerCoreConfig, EcPlayerState, EnvInfo } from '@give-tech/ec-player';
5
+ export { LogLevel } from '@give-tech/ec-player';
6
+ /**
7
+ * WASM 路径配置
8
+ */
9
+ export interface WasmPathConfig {
10
+ /**
11
+ * SIMD 版本 WASM 路径(推荐,性能更好)
12
+ * 用于支持 SIMD 的现代浏览器
13
+ */
14
+ simd?: string;
15
+ /**
16
+ * 非 SIMD 版本 WASM 路径
17
+ * 用于不支持 SIMD 的旧浏览器作为降级方案
18
+ */
19
+ nonSimd?: string;
20
+ }
21
+ /**
22
+ * 全局配置
23
+ *
24
+ * 必须在使用 EcPlayer 组件前调用
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // main.ts
29
+ * import { configureEcPlayer } from '@give-tech/ec-player-vue'
30
+ *
31
+ * // 配置 SIMD 和非 SIMD 路径,支持自动降级
32
+ * configureEcPlayer({
33
+ * wasm: {
34
+ * simd: '/wasm/decoder-simd.js',
35
+ * nonSimd: '/wasm/decoder.js'
36
+ * }
37
+ * })
38
+ *
39
+ * // 使用 CDN
40
+ * configureEcPlayer({
41
+ * wasm: {
42
+ * simd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder-simd.js',
43
+ * nonSimd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder.js'
44
+ * }
45
+ * })
46
+ * ```
47
+ */
48
+ export interface EcPlayerGlobalConfig {
49
+ /**
50
+ * WASM 路径配置(必填)
51
+ * 支持 SIMD 和非 SIMD 自动降级
52
+ */
53
+ wasm?: WasmPathConfig;
54
+ /** 默认是否显示控制条 */
55
+ showControls?: boolean;
56
+ /** 默认缓冲目标帧数 */
57
+ targetBufferSize?: number;
58
+ /** 默认每批解码帧数 */
59
+ decodeBatchSize?: number;
60
+ /** 默认队列上限 */
61
+ maxQueueSize?: number;
62
+ }
63
+ /**
64
+ * 播放器配置选项
65
+ */
66
+ export interface PlayerOptions {
67
+ /** 帧缓冲区目标大小 */
68
+ targetBufferSize?: number;
69
+ /** 每批解码帧数 */
70
+ decodeBatchSize?: number;
71
+ /** NAL/Sample 队列上限 */
72
+ maxQueueSize?: number;
73
+ }
74
+ /**
75
+ * 视频源配置
76
+ */
77
+ export interface EcPlayerSource {
78
+ /** 视频 URL */
79
+ url: string;
80
+ /** 是否为直播流 */
81
+ isLive?: boolean;
82
+ /** 版本号,用于强制重新加载 */
83
+ version?: number;
84
+ }
85
+ /**
86
+ * 控制条选项
87
+ */
88
+ export interface EcPlayerControlOption {
89
+ /** 是否显示控制条 */
90
+ show?: boolean;
91
+ /** 是否显示全屏按钮 */
92
+ fullscreen?: boolean;
93
+ /** 是否显示环境信息按钮 */
94
+ info?: boolean;
95
+ }
96
+ /**
97
+ * EcPlayer 组件 Props
98
+ */
99
+ export interface EcPlayerProps {
100
+ /** 视频源 */
101
+ src?: EcPlayerSource;
102
+ /** 控制条选项 */
103
+ controlOption?: EcPlayerControlOption;
104
+ }
105
+ /**
106
+ * EcPlayer 组件 Emits
107
+ */
108
+ export interface EcPlayerEmits {
109
+ /** 播放时触发 */
110
+ (e: 'play'): void;
111
+ /** 暂停时触发 */
112
+ (e: 'pause'): void;
113
+ }
114
+ /**
115
+ * EcPlayer 组件 Expose 方法
116
+ */
117
+ export interface EcPlayerExpose {
118
+ /** 播放 */
119
+ play: () => void;
120
+ /** 暂停 */
121
+ pause: () => void;
122
+ /** 跳转到指定时间(毫秒) */
123
+ seek: (time: number) => Promise<void>;
124
+ }
125
+ /**
126
+ * 环境信息面板数据
127
+ */
128
+ export interface EnvPanelData {
129
+ envInfo: EnvInfo | null;
130
+ state: Partial<EcPlayerState>;
131
+ }
132
+ /**
133
+ * 播放器配置(从 Props 转换)
134
+ */
135
+ export interface PlayerConfigFromProps extends EcPlayerCoreConfig {
136
+ wasmPath: string;
137
+ targetBufferSize: number;
138
+ decodeBatchSize: number;
139
+ maxQueueSize: number;
140
+ }
141
+ /**
142
+ * 默认 Props 值
143
+ */
144
+ export declare const DEFAULT_EC_PLAYER_PROPS: {
145
+ src: EcPlayerSource | undefined;
146
+ controlOption: {
147
+ show: boolean;
148
+ fullscreen: boolean;
149
+ info: boolean;
150
+ };
151
+ };
152
+ /**
153
+ * 默认播放器配置
154
+ */
155
+ export declare const DEFAULT_PLAYER_OPTIONS: Required<PlayerOptions>;
156
+ /**
157
+ * 默认 WASM 配置
158
+ */
159
+ export declare const DEFAULT_WASM_CONFIG: WasmPathConfig;
160
+ /**
161
+ * 配置 EcPlayer 全局默认值
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * // main.ts
166
+ * import { configureEcPlayer } from '@give-tech/ec-player-vue'
167
+ *
168
+ * // 配置 SIMD 和非 SIMD 路径,支持自动降级
169
+ * configureEcPlayer({
170
+ * wasm: {
171
+ * simd: '/wasm/decoder-simd.js',
172
+ * nonSimd: '/wasm/decoder.js'
173
+ * }
174
+ * })
175
+ *
176
+ * // 使用 CDN
177
+ * configureEcPlayer({
178
+ * wasm: {
179
+ * simd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder-simd.js',
180
+ * nonSimd: 'https://unpkg.com/@give-tech/ec-player@latest/wasm/decoder.js'
181
+ * }
182
+ * })
183
+ * ```
184
+ */
185
+ export declare function configureEcPlayer(config: EcPlayerGlobalConfig): void;
186
+ /**
187
+ * 获取全局配置
188
+ */
189
+ export declare function getGlobalConfig(): EcPlayerGlobalConfig;
190
+ /**
191
+ * 获取 WASM 路径配置
192
+ */
193
+ export declare function getWasmConfig(): WasmPathConfig;
194
+ /**
195
+ * 获取合并后的配置(props > 全局配置 > 默认值)
196
+ */
197
+ export declare function getMergedConfig(props: EcPlayerProps): {
198
+ src: EcPlayerSource | undefined;
199
+ controlOption: {
200
+ show: boolean;
201
+ fullscreen: boolean;
202
+ info: boolean;
203
+ };
204
+ };
205
+ /**
206
+ * 获取合并后的播放器配置(options > 全局配置 > 默认值)
207
+ */
208
+ export declare function getMergedPlayerOptions(options: PlayerOptions): Required<PlayerOptions>;
209
+ /**
210
+ * 根据环境能力选择合适的 WASM 路径
211
+ *
212
+ * @param hasSimdSupport 是否支持 SIMD
213
+ * @returns 实际使用的 WASM 路径
214
+ */
215
+ export declare function resolveWasmPath(hasSimdSupport: boolean): string;
216
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@give-tech/ec-player-vue",
3
- "version": "0.0.1-beta.31",
3
+ "version": "0.0.1-beta.33",
4
4
  "description": "EcPlayer Vue 3 Component - HLS/FLV Streaming Video Player",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,12 +21,13 @@
21
21
  "dev": "vite build --watch",
22
22
  "typecheck": "tsc --noEmit"
23
23
  },
24
+ "dependencies": {
25
+ "@give-tech/ec-player": "^0.0.1-beta.31"
26
+ },
24
27
  "peerDependencies": {
25
- "@give-tech/ec-player": "^0.0.1-beta.2",
26
28
  "vue": "^3.3.0"
27
29
  },
28
30
  "devDependencies": {
29
- "@give-tech/ec-player": "^0.0.1-beta.29",
30
31
  "@vitejs/plugin-vue": "^6.0.4",
31
32
  "typescript": "^5.9.3",
32
33
  "vite": "^7.3.1",
@@ -52,8 +53,5 @@
52
53
  "license": "MIT",
53
54
  "publishConfig": {
54
55
  "access": "public"
55
- },
56
- "dependencies": {
57
- "@give-tech/ec-player-vue": "^0.0.1-beta.28"
58
56
  }
59
57
  }