mikuru 1.0.30 → 1.0.31

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.31 - 2026-05-16
4
+
5
+ - Exported `MikuruVideoPlayer` media events so parent components can listen for playback, timing, seeking, volume, and playback-rate changes with typed media state payloads.
6
+ - Exported matching `MikuruAudioPlayer` media events and documented the shared media player event payload.
7
+
3
8
  ## 1.0.30 - 2026-05-15
4
9
 
5
10
  - Hardened `MikuruVideoPlayer` controls so stop, mute, playback speed, seeking, and modal close operations do not trigger recursive updates while browser media events are firing.
@@ -4,12 +4,15 @@
4
4
  ref="mediaEl"
5
5
  :src="src"
6
6
  :preload="preload"
7
- @loadedmetadata="syncMedia"
8
- @timeupdate="syncMedia"
9
- @durationchange="syncMedia"
7
+ @loadedmetadata="handleLoadedMetadata"
8
+ @timeupdate="handleTimeUpdate"
9
+ @durationchange="handleDurationChange"
10
10
  @play="markPlaying"
11
11
  @pause="markPaused"
12
- @ended="markPaused"
12
+ @ended="markEnded"
13
+ @seeked="handleSeeked"
14
+ @volumechange="handleVolumeChange"
15
+ @ratechange="handleRateChange"
13
16
  ></audio>
14
17
 
15
18
  <div class="art">
@@ -61,6 +64,18 @@ const {
61
64
  preload: String
62
65
  });
63
66
 
67
+ const emit = defineEmits([
68
+ "loadedmetadata",
69
+ "timeupdate",
70
+ "durationchange",
71
+ "play",
72
+ "pause",
73
+ "ended",
74
+ "seeked",
75
+ "volumechange",
76
+ "ratechange"
77
+ ]);
78
+
64
79
  const mediaEl = ref(null);
65
80
  const currentTime = ref(0);
66
81
  const duration = ref(0);
@@ -107,6 +122,58 @@ function syncMedia() {
107
122
  duration.value = Number.isFinite(media.duration) ? media.duration : 0;
108
123
  }
109
124
 
125
+ function createMediaPayload(event) {
126
+ const media = getMedia();
127
+ if (!media) return null;
128
+ return {
129
+ currentTime: media.currentTime || 0,
130
+ duration: Number.isFinite(media.duration) ? media.duration : 0,
131
+ paused: media.paused,
132
+ ended: media.ended,
133
+ muted: media.muted,
134
+ volume: media.volume,
135
+ playbackRate: media.playbackRate,
136
+ nativeEvent: event
137
+ };
138
+ }
139
+
140
+ function emitMediaPayload(event, dispatch) {
141
+ const payload = createMediaPayload(event);
142
+ if (payload) {
143
+ dispatch(payload);
144
+ }
145
+ }
146
+
147
+ function handleLoadedMetadata(event) {
148
+ syncMedia();
149
+ emitMediaPayload(event, (payload) => emit("loadedmetadata", payload));
150
+ }
151
+
152
+ function handleTimeUpdate(event) {
153
+ syncMedia();
154
+ emitMediaPayload(event, (payload) => emit("timeupdate", payload));
155
+ }
156
+
157
+ function handleDurationChange(event) {
158
+ syncMedia();
159
+ emitMediaPayload(event, (payload) => emit("durationchange", payload));
160
+ }
161
+
162
+ function handleSeeked(event) {
163
+ syncMedia();
164
+ emitMediaPayload(event, (payload) => emit("seeked", payload));
165
+ }
166
+
167
+ function handleVolumeChange(event) {
168
+ syncMedia();
169
+ emitMediaPayload(event, (payload) => emit("volumechange", payload));
170
+ }
171
+
172
+ function handleRateChange(event) {
173
+ syncMedia();
174
+ emitMediaPayload(event, (payload) => emit("ratechange", payload));
175
+ }
176
+
110
177
  function applyAudioSettings() {
111
178
  if (isDisposed) return;
112
179
  const media = getMedia();
@@ -115,14 +182,22 @@ function applyAudioSettings() {
115
182
  media.muted = muted.value;
116
183
  }
117
184
 
118
- function markPlaying() {
185
+ function markPlaying(event) {
119
186
  if (isDisposed || shouldIgnoreMediaEvent()) return;
120
187
  isPlaying.value = true;
188
+ emitMediaPayload(event, (payload) => emit("play", payload));
189
+ }
190
+
191
+ function markPaused(event) {
192
+ if (isDisposed || shouldIgnoreMediaEvent()) return;
193
+ isPlaying.value = false;
194
+ emitMediaPayload(event, (payload) => emit("pause", payload));
121
195
  }
122
196
 
123
- function markPaused() {
197
+ function markEnded(event) {
124
198
  if (isDisposed || shouldIgnoreMediaEvent()) return;
125
199
  isPlaying.value = false;
200
+ emitMediaPayload(event, (payload) => emit("ended", payload));
126
201
  }
127
202
 
128
203
  async function togglePlayback() {
@@ -17,12 +17,15 @@
17
17
  :poster="poster"
18
18
  :preload="preload"
19
19
  playsinline
20
- @loadedmetadata="syncMedia"
21
- @timeupdate="syncMedia"
22
- @durationchange="syncMedia"
20
+ @loadedmetadata="handleLoadedMetadata"
21
+ @timeupdate="handleTimeUpdate"
22
+ @durationchange="handleDurationChange"
23
23
  @play="markPlaying"
24
24
  @pause="markPaused"
25
- @ended="markPaused"
25
+ @ended="markEnded"
26
+ @seeked="handleSeeked"
27
+ @volumechange="handleVolumeChange"
28
+ @ratechange="handleRateChange"
26
29
  @click="togglePlayback"
27
30
  ></video>
28
31
 
@@ -104,6 +107,18 @@ const {
104
107
  preload: String
105
108
  });
106
109
 
110
+ const emit = defineEmits([
111
+ "loadedmetadata",
112
+ "timeupdate",
113
+ "durationchange",
114
+ "play",
115
+ "pause",
116
+ "ended",
117
+ "seeked",
118
+ "volumechange",
119
+ "ratechange"
120
+ ]);
121
+
107
122
  const mediaEl = ref(null);
108
123
  const screenEl = ref(null);
109
124
  const currentTime = ref(0);
@@ -168,6 +183,58 @@ function syncMedia() {
168
183
  duration.value = Number.isFinite(media.duration) ? media.duration : 0;
169
184
  }
170
185
 
186
+ function createMediaPayload(event) {
187
+ const media = getMedia();
188
+ if (!media) return null;
189
+ return {
190
+ currentTime: media.currentTime || 0,
191
+ duration: Number.isFinite(media.duration) ? media.duration : 0,
192
+ paused: media.paused,
193
+ ended: media.ended,
194
+ muted: media.muted,
195
+ volume: media.volume,
196
+ playbackRate: media.playbackRate,
197
+ nativeEvent: event
198
+ };
199
+ }
200
+
201
+ function emitMediaPayload(event, dispatch) {
202
+ const payload = createMediaPayload(event);
203
+ if (payload) {
204
+ dispatch(payload);
205
+ }
206
+ }
207
+
208
+ function handleLoadedMetadata(event) {
209
+ syncMedia();
210
+ emitMediaPayload(event, (payload) => emit("loadedmetadata", payload));
211
+ }
212
+
213
+ function handleTimeUpdate(event) {
214
+ syncMedia();
215
+ emitMediaPayload(event, (payload) => emit("timeupdate", payload));
216
+ }
217
+
218
+ function handleDurationChange(event) {
219
+ syncMedia();
220
+ emitMediaPayload(event, (payload) => emit("durationchange", payload));
221
+ }
222
+
223
+ function handleSeeked(event) {
224
+ syncMedia();
225
+ emitMediaPayload(event, (payload) => emit("seeked", payload));
226
+ }
227
+
228
+ function handleVolumeChange(event) {
229
+ syncMedia();
230
+ emitMediaPayload(event, (payload) => emit("volumechange", payload));
231
+ }
232
+
233
+ function handleRateChange(event) {
234
+ syncMedia();
235
+ emitMediaPayload(event, (payload) => emit("ratechange", payload));
236
+ }
237
+
171
238
  function applyAudioSettings() {
172
239
  if (isDisposed) return;
173
240
  const media = getMedia();
@@ -177,16 +244,25 @@ function applyAudioSettings() {
177
244
  media.playbackRate = playbackRate;
178
245
  }
179
246
 
180
- function markPlaying() {
247
+ function markPlaying(event) {
181
248
  if (isDisposed || shouldIgnoreMediaEvent()) return;
182
249
  isPlaying.value = true;
183
250
  controlsVisible.value = pointerInside.value;
251
+ emitMediaPayload(event, (payload) => emit("play", payload));
252
+ }
253
+
254
+ function markPaused(event) {
255
+ if (isDisposed || shouldIgnoreMediaEvent()) return;
256
+ isPlaying.value = false;
257
+ controlsVisible.value = true;
258
+ emitMediaPayload(event, (payload) => emit("pause", payload));
184
259
  }
185
260
 
186
- function markPaused() {
261
+ function markEnded(event) {
187
262
  if (isDisposed || shouldIgnoreMediaEvent()) return;
188
263
  isPlaying.value = false;
189
264
  controlsVisible.value = true;
265
+ emitMediaPayload(event, (payload) => emit("ended", payload));
190
266
  }
191
267
 
192
268
  function updateFullscreen() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikuru",
3
- "version": "1.0.30",
3
+ "version": "1.0.31",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "A compile-first JavaScript framework with Vue-like authoring and Svelte-like generated DOM updates.",
@@ -1,11 +1,34 @@
1
1
  import type { MikuruComponent } from "../env";
2
2
 
3
+ export type MikuruAudioPlayerEventPayload = {
4
+ currentTime: number;
5
+ duration: number;
6
+ paused: boolean;
7
+ ended: boolean;
8
+ muted: boolean;
9
+ volume: number;
10
+ playbackRate: number;
11
+ nativeEvent?: Event;
12
+ };
13
+
14
+ export type MikuruAudioPlayerEvents = {
15
+ onLoadedmetadata?: (payload: MikuruAudioPlayerEventPayload) => void;
16
+ onTimeupdate?: (payload: MikuruAudioPlayerEventPayload) => void;
17
+ onDurationchange?: (payload: MikuruAudioPlayerEventPayload) => void;
18
+ onPlay?: (payload: MikuruAudioPlayerEventPayload) => void;
19
+ onPause?: (payload: MikuruAudioPlayerEventPayload) => void;
20
+ onEnded?: (payload: MikuruAudioPlayerEventPayload) => void;
21
+ onSeeked?: (payload: MikuruAudioPlayerEventPayload) => void;
22
+ onVolumechange?: (payload: MikuruAudioPlayerEventPayload) => void;
23
+ onRatechange?: (payload: MikuruAudioPlayerEventPayload) => void;
24
+ };
25
+
3
26
  export type MikuruAudioPlayerProps = {
4
27
  src: string;
5
28
  title?: string;
6
29
  artist?: string;
7
30
  preload?: string;
8
- };
31
+ } & MikuruAudioPlayerEvents;
9
32
 
10
33
  declare const component: MikuruComponent<MikuruAudioPlayerProps>;
11
34
  export default component;
@@ -1,12 +1,35 @@
1
1
  import type { MikuruComponent } from "../env";
2
2
 
3
+ export type MikuruVideoPlayerEventPayload = {
4
+ currentTime: number;
5
+ duration: number;
6
+ paused: boolean;
7
+ ended: boolean;
8
+ muted: boolean;
9
+ volume: number;
10
+ playbackRate: number;
11
+ nativeEvent?: Event;
12
+ };
13
+
14
+ export type MikuruVideoPlayerEvents = {
15
+ onLoadedmetadata?: (payload: MikuruVideoPlayerEventPayload) => void;
16
+ onTimeupdate?: (payload: MikuruVideoPlayerEventPayload) => void;
17
+ onDurationchange?: (payload: MikuruVideoPlayerEventPayload) => void;
18
+ onPlay?: (payload: MikuruVideoPlayerEventPayload) => void;
19
+ onPause?: (payload: MikuruVideoPlayerEventPayload) => void;
20
+ onEnded?: (payload: MikuruVideoPlayerEventPayload) => void;
21
+ onSeeked?: (payload: MikuruVideoPlayerEventPayload) => void;
22
+ onVolumechange?: (payload: MikuruVideoPlayerEventPayload) => void;
23
+ onRatechange?: (payload: MikuruVideoPlayerEventPayload) => void;
24
+ };
25
+
3
26
  export type MikuruVideoPlayerProps = {
4
27
  src: string;
5
28
  poster?: string;
6
29
  title?: string;
7
30
  subtitle?: string;
8
31
  preload?: string;
9
- };
32
+ } & MikuruVideoPlayerEvents;
10
33
 
11
34
  declare const component: MikuruComponent<MikuruVideoPlayerProps>;
12
35
  export default component;