|
14 | 14 | role="region" |
15 | 15 | :aria-label="$tr('articleContent')" |
16 | 16 | > |
17 | | - <SafeHTML :html="html" /> |
| 17 | + <SafeHTML |
| 18 | + :html="html" |
| 19 | + @startTracking="handleViewerStartTracking" |
| 20 | + @stopTracking="handleViewerStopTracking" |
| 21 | + @updateProgress="handleViewerUpdateProgress" |
| 22 | + @addProgress="handleViewerAddProgress" |
| 23 | + @finished="handleViewerFinished" |
| 24 | + /> |
18 | 25 | </div> |
19 | 26 | </div> |
20 | 27 |
|
|
52 | 59 | html: null, |
53 | 60 | scrollBasedProgress: 0, |
54 | 61 | debouncedHandleScroll: null, |
| 62 | + // Track embedded viewers for progress aggregation |
| 63 | + // Using object instead of Map for Vue 2.7 reactivity |
| 64 | + // Structure: { viewerId: { progress: number, complete: boolean } } |
| 65 | + embeddedViewers: {}, |
| 66 | + // Guard to prevent emitting 'finished' multiple times |
| 67 | + hasEmittedFinished: false, |
55 | 68 | }; |
56 | 69 | }, |
57 | 70 | computed: { |
58 | 71 | entry() { |
59 | 72 | return (this.options && this.options.entry) || 'index.html'; |
60 | 73 | }, |
| 74 | + // Count of registered embedded viewers |
| 75 | + viewerCount() { |
| 76 | + return Object.keys(this.embeddedViewers).length; |
| 77 | + }, |
| 78 | + // Aggregated progress using dynamic weighting |
| 79 | + // If no embedded viewers: progress = scrollBasedProgress |
| 80 | + // If viewers exist: progress = (scrollBasedProgress + avgViewerProgress) / 2 |
| 81 | + aggregatedProgress() { |
| 82 | + if (this.viewerCount === 0) { |
| 83 | + return this.scrollBasedProgress; |
| 84 | + } |
| 85 | +
|
| 86 | + let totalViewerProgress = 0; |
| 87 | + for (const viewer of Object.values(this.embeddedViewers)) { |
| 88 | + totalViewerProgress += viewer.progress; |
| 89 | + } |
| 90 | + const avgViewerProgress = totalViewerProgress / this.viewerCount; |
| 91 | +
|
| 92 | + // Dynamic weighting: 50% scroll, 50% viewers |
| 93 | + return (this.scrollBasedProgress + avgViewerProgress) / 2; |
| 94 | + }, |
| 95 | + // Whether all sources are complete |
| 96 | + allSourcesComplete() { |
| 97 | + // All viewers must be complete |
| 98 | + for (const viewer of Object.values(this.embeddedViewers)) { |
| 99 | + if (!viewer.complete) { |
| 100 | + return false; |
| 101 | + } |
| 102 | + } |
| 103 | +
|
| 104 | + return true; |
| 105 | + }, |
61 | 106 | }, |
62 | 107 | async created() { |
63 | 108 | const storageUrl = this.defaultFile.storage_url; |
|
100 | 145 | } |
101 | 146 | }); |
102 | 147 | }, |
| 148 | +
|
| 149 | + // Handle startTracking from embedded viewers |
| 150 | + handleViewerStartTracking(viewerId) { |
| 151 | + if (viewerId && !this.embeddedViewers[viewerId]) { |
| 152 | + this.$set(this.embeddedViewers, viewerId, { progress: 0, complete: false }); |
| 153 | + } |
| 154 | + }, |
| 155 | +
|
| 156 | + // Handle stopTracking from embedded viewers |
| 157 | + handleViewerStopTracking(viewerId) { |
| 158 | + if (viewerId) { |
| 159 | + this.$delete(this.embeddedViewers, viewerId); |
| 160 | + } |
| 161 | + }, |
| 162 | +
|
| 163 | + // Handle updateProgress from embedded viewers |
| 164 | + handleViewerUpdateProgress(progress, viewerId) { |
| 165 | + if (viewerId && this.embeddedViewers[viewerId]) { |
| 166 | + this.$set(this.embeddedViewers, viewerId, { |
| 167 | + ...this.embeddedViewers[viewerId], |
| 168 | + progress: Math.min(1, Math.max(0, progress)), |
| 169 | + }); |
| 170 | + } |
| 171 | + }, |
| 172 | +
|
| 173 | + // Handle addProgress from embedded viewers |
| 174 | + handleViewerAddProgress(delta, viewerId) { |
| 175 | + if (viewerId) { |
| 176 | + const viewer = this.embeddedViewers[viewerId]; |
| 177 | + if (viewer) { |
| 178 | + const newProgress = Math.min(1, Math.max(0, viewer.progress + delta)); |
| 179 | + this.$set(this.embeddedViewers, viewerId, { |
| 180 | + ...viewer, |
| 181 | + progress: newProgress, |
| 182 | + }); |
| 183 | + } |
| 184 | + } |
| 185 | + }, |
| 186 | +
|
| 187 | + // Handle finished from embedded viewers |
| 188 | + handleViewerFinished(viewerId) { |
| 189 | + if (viewerId && this.embeddedViewers[viewerId]) { |
| 190 | + this.$set(this.embeddedViewers, viewerId, { |
| 191 | + progress: 1, |
| 192 | + complete: true, |
| 193 | + }); |
| 194 | + } |
| 195 | + }, |
| 196 | +
|
103 | 197 | recordProgress() { |
104 | 198 | let progress; |
105 | 199 | if (this.forceDurationBasedProgress) { |
106 | 200 | progress = this.durationBasedProgress; |
107 | 201 | } else { |
108 | | - // Use scroll events to track progress |
109 | | - progress = this.scrollBasedProgress; |
| 202 | + // Use aggregated progress from scroll + embedded viewers |
| 203 | + progress = this.aggregatedProgress; |
110 | 204 | } |
111 | 205 | this.$emit('updateProgress', progress); |
112 | | -
|
113 | | - if (progress >= 1) { |
| 206 | + if (progress >= 1 && this.allSourcesComplete && !this.hasEmittedFinished) { |
114 | 207 | this.$emit('finished'); |
| 208 | + this.hasEmittedFinished = true; |
115 | 209 | } |
116 | 210 | this.pollProgress(); |
117 | 211 | }, |
|
0 commit comments