@mindedge/vuetify-player 0.1.3 → 0.3.0

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/README.md CHANGED
@@ -5,13 +5,13 @@ An accessible, localized, full featured media player with Vuetifyjs
5
5
  ## Table of Contents
6
6
 
7
7
  - [Quick Start](#quick-start)
8
- - [View demos locally](#local-project-setup-to-view-demos)
9
8
  - [Complete media source structure](#full-media-src-structure)
10
9
  - [Define a media source](#the-src-attribute)
11
10
  - [Define a playlist](#the-playlist-attribute)
12
11
  - [Define ads / preroll / postroll](#the-ads-array)
13
12
  - [Supported Attributes](#supported-vuetifyplayer-attributes)
14
13
  - [Supported Events](#supported-vuetifyplayer-events)
14
+ - [Captions](#captions)
15
15
  - [License](#license)
16
16
 
17
17
  ---
@@ -60,26 +60,6 @@ src: {
60
60
 
61
61
  ### 4. Enjoy~
62
62
 
63
- ## Local project setup to view demos
64
-
65
- Clone the repo
66
-
67
- ```bash
68
- git clone https://github.com/mindedge/vuetify-player.git
69
- ```
70
-
71
- Install necessary packages
72
-
73
- ```bash
74
- npm install
75
- ```
76
-
77
- Compile and serve. This also hot-reloads for development & testing
78
-
79
- ```bash
80
- npm run serve
81
- ```
82
-
83
63
  ## Full media `src` structure
84
64
 
85
65
  ```javascript
@@ -238,29 +218,51 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
238
218
 
239
219
  ## Supported `<VuetifyPlayer>` Events
240
220
 
241
- | Event name | Returns | When it's triggered |
242
- | ------------------------ | ----------------- | -------------------------------------------------------------------------------------------------------- |
243
- | `abort` | `Event` | Download interrupted |
244
- | `canplay` | `Event` | Playback can start |
245
- | `canplaythrough` | `Event` | Playback can continue and should not be interrupted. Readstate is 3 |
246
- | `emptied` | `Event` | The network connection is down |
247
- | `ended` | `Event` | When playback has stopped because the end of the media was reached |
248
- | `error` | `Event` | A network error occurred during the download |
249
- | `loadeddata` | `Event` | When the frame at the current playback position of the media has finished loading; often the first frame |
250
- | `loadedmetadata` | `Event` | When the metadata has been loaded |
251
- | `play` | `Event` | The media has received a request to start playing |
252
- | `pause` | `Event` | Playback has been suspended |
253
- | `progress` | `Event` | The progress event is fired periodically as the browser loads a resource. |
254
- | `seeking` | `Event` | Playback has moved to a new location |
255
- | `timeupdate` | `Object` | The current time was changed. Object contains { event: Event, current_percent: Number } |
256
- | `ratechange` | `Number` | The playback speed multiplier |
257
- | `stalled` | `Event` | The browser tried to download but has not received data yet |
258
- | `volumechange` | `Number` | The volume or muted button changed. Value from 0.0 to 1 |
259
- | `waiting` | `Event` | Pause playback to download more data |
260
- | `click:fullscreen` | `true` \| `false` | When the fullscreen button is clicked. true on fullscreen, false on exiting fullscreen |
261
- | `click:pictureinpicture` | `true` \| `false` | When the picture-in-picture button is clicked. true on enabled, false on disabled |
262
- | `mouseover` | `MouseEvent` | Mouse over the media |
263
- | `mouseout` | `MouseEvent` | Mouse left the media |
221
+ | Event name | Returns | When it's triggered |
222
+ | -------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- |
223
+ | `abort` | `Event` | Download interrupted |
224
+ | `canplay` | `Event` | Playback can start |
225
+ | `canplaythrough` | `Event` | Playback can continue and should not be interrupted. Readstate is 3 |
226
+ | `emptied` | `Event` | The network connection is down |
227
+ | `ended` | `Event` | When playback has stopped because the end of the media was reached |
228
+ | `error` | `Event` | A network error occurred during the download |
229
+ | `loadeddata` | `Event` | When the frame at the current playback position of the media has finished loading; often the first frame |
230
+ | `loadedmetadata` | `Event` | When the metadata has been loaded |
231
+ | `play` | `Event` | The media has received a request to start playing |
232
+ | `pause` | `Event` | Playback has been suspended |
233
+ | `progress` | `Event` | The progress event is fired periodically as the browser loads a resource. |
234
+ | `seeking` | `Event` | Playback has moved to a new location |
235
+ | `timeupdate` | `Object` | The current time was changed. Object contains { event: Event, current_percent: Number } |
236
+ | `ratechange` | `Number` | The playback speed multiplier |
237
+ | `stalled` | `Event` | The browser tried to download but has not received data yet |
238
+ | `volumechange` | `Number` | The volume or muted button changed. Value from 0.0 to 1 |
239
+ | `waiting` | `Event` | Pause playback to download more data |
240
+ | `click:fullscreen` | `true` \| `false` | When the fullscreen button is clicked. true on fullscreen, false on exiting fullscreen |
241
+ | `click:pictureinpicture` | `true` \| `false` | When the picture-in-picture button is clicked. true on enabled, false on disabled |
242
+ | `click:captions-expand` | `true` \| `false` | When the expand captions button is clicked. true on expanded, false on collapsed |
243
+ | `click:captions-paragraph` | `true` \| `false` | When the view as paragraph button is clicked. true when viewing as a paragraph, false when viewing as timed captions |
244
+ | `mouseover` | `MouseEvent` | Mouse over the media |
245
+ | `mouseout` | `MouseEvent` | Mouse left the media |
246
+
247
+ ## Captions
248
+
249
+ The player supports `.vtt` captions as defined in the `tracks` array explained above.
250
+
251
+ Additionally we support the tag `<c.transcript> ... </c>` inline with your captions text. This tag will omit the enclosed text from the video player captions overlay but show the text in the separate interactive captions panel.
252
+ This allows you to include additional information that you want to appear in the transcript that might not be appropriate to display in the video player itself.
253
+
254
+ Below is a `sample.vtt` on how to use the `<c.transcript>` tag.
255
+
256
+ ```
257
+ WEBVTT
258
+
259
+ 00:00:00.000 --> 00:00:03.999
260
+ This text will show. <c.transcript>This text is hidden from the player.</c> This is some more text to show.
261
+ This text will show up on a new line in the player.
262
+
263
+ 00:00:03.000 --> 00:00:5.999
264
+ sentence here to break it up
265
+ ```
264
266
 
265
267
  ## License
266
268
 
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: ['@vue/cli-plugin-babel/preset'],
3
+ }
package/jest.config.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ preset: '@vue/cli-plugin-unit-jest',
3
+ }
package/jsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "module": "esnext",
5
+ "baseUrl": "./",
6
+ "moduleResolution": "node",
7
+ "paths": {
8
+ "@/*": ["src/*"]
9
+ },
10
+ "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,18 +1,16 @@
1
1
  {
2
2
  "name": "@mindedge/vuetify-player",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Accessible, localized, full featured media player with Vuetifyjs",
6
6
  "author": "Jacob Rogaishio",
7
7
  "scripts": {
8
- "serve": "vue-cli-service serve",
9
- "build": "vue-cli-service build --target lib --name VuetifyPlayer src/index.js",
10
- "test:unit": "vue-cli-service test:unit",
8
+ "test": "vue-cli-service test:unit",
11
9
  "lint": "vue-cli-service lint",
12
10
  "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"",
13
11
  "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore ."
14
12
  },
15
- "main": "./dist/VuetifyPlayer.umd.min.js",
13
+ "main": "./src/components/VuetifyPlayer.vue",
16
14
  "dependencies": {
17
15
  "@intlify/vue-i18n-loader": "^1.1.0",
18
16
  "core-js": "^3.8.3",
@@ -0,0 +1,324 @@
1
+ <template>
2
+ <v-card>
3
+ <v-card-actions class="justify-end">
4
+ <v-tooltip top>
5
+ <template v-slot:activator="{ on, attrs }">
6
+ <v-btn
7
+ color="primary"
8
+ text
9
+ v-bind="attrs"
10
+ v-on="on"
11
+ @click="onClickToggleParagraphView"
12
+ >
13
+ <v-icon>{{
14
+ paragraphView
15
+ ? 'mdi-closed-caption-outline'
16
+ : 'mdi-text-box-outline'
17
+ }}</v-icon>
18
+ <span class="sr-only">{{
19
+ paragraphView
20
+ ? t(language, 'captions.view_as_captions')
21
+ : t(language, 'captions.view_as_paragraph')
22
+ }}</span>
23
+ </v-btn></template
24
+ >
25
+ <span>{{
26
+ paragraphView
27
+ ? t(language, 'captions.view_as_captions')
28
+ : t(language, 'captions.view_as_paragraph')
29
+ }}</span>
30
+ </v-tooltip>
31
+ <v-tooltip top>
32
+ <template v-slot:activator="{ on, attrs }">
33
+ <v-btn
34
+ color="primary"
35
+ text
36
+ v-bind="attrs"
37
+ v-on="on"
38
+ @click="onClickToggleExpand"
39
+ >
40
+ <v-icon>{{
41
+ expanded ? 'mdi-arrow-collapse' : 'mdi-arrow-expand'
42
+ }}</v-icon>
43
+ <span class="sr-only">{{
44
+ expanded
45
+ ? t(language, 'captions.collapse')
46
+ : t(language, 'captions.expand')
47
+ }}</span>
48
+ </v-btn></template
49
+ >
50
+ <span>{{
51
+ expanded
52
+ ? t(language, 'captions.collapse')
53
+ : t(language, 'captions.expand')
54
+ }}</span>
55
+ </v-tooltip>
56
+ </v-card-actions>
57
+ <v-card-text>
58
+ <v-list ref="captionList" :class="captionsList">
59
+ <v-list-item-group v-model="captionIndex">
60
+ <v-list-item
61
+ ref="captionItems"
62
+ v-for="(cue, index) in cues"
63
+ :key="index"
64
+ :two-line="expanded"
65
+ @click="onCueClick(cue.startTime)"
66
+ >
67
+ <template v-if="!expanded">
68
+ <v-list-item-icon v-if="!paragraphView">
69
+ <v-icon
70
+ >{{
71
+ index === captionIndex
72
+ ? 'mdi-arrow-right-drop-circle-outline'
73
+ : 'mdi-checkbox-blank-circle-outline'
74
+ }}
75
+ </v-icon>
76
+ </v-list-item-icon>
77
+ <v-list-item-content>
78
+ <v-list-item-title
79
+ v-html="cue.rawText || cue.text"
80
+ class="caption-text"
81
+ ></v-list-item-title>
82
+ </v-list-item-content>
83
+ <v-list-item-action v-if="!paragraphView">
84
+ <span aria-hidden="true">
85
+ {{
86
+ filters.playerShortDuration(
87
+ cue.startTime
88
+ )
89
+ }}
90
+ -
91
+ {{
92
+ filters.playerShortDuration(cue.endTime)
93
+ }}
94
+ </span>
95
+ </v-list-item-action>
96
+ </template>
97
+ <template v-if="expanded">
98
+ <v-list-item-content>
99
+ <v-list-item-title
100
+ v-html="cue.rawText || cue.text"
101
+ class="caption-text"
102
+ ></v-list-item-title>
103
+ <v-list-item-subtitle v-if="!paragraphView">
104
+ <v-icon
105
+ >{{
106
+ index === captionIndex
107
+ ? 'mdi-arrow-right-drop-circle-outline'
108
+ : 'mdi-checkbox-blank-circle-outline'
109
+ }}
110
+ </v-icon>
111
+ <span aria-hidden="true">
112
+ {{
113
+ filters.playerShortDuration(
114
+ cue.startTime
115
+ )
116
+ }}
117
+ -
118
+ {{
119
+ filters.playerShortDuration(
120
+ cue.endTime
121
+ )
122
+ }}
123
+ </span>
124
+ </v-list-item-subtitle>
125
+ </v-list-item-content>
126
+ </template>
127
+ </v-list-item>
128
+ </v-list-item-group>
129
+ </v-list>
130
+ </v-card-text>
131
+ </v-card>
132
+ </template>
133
+
134
+ <script>
135
+ import filters from '../filters'
136
+ import { t } from '../../i18n/i18n'
137
+
138
+ export default {
139
+ props: {
140
+ value: { type: [Object, Array], required: true },
141
+ language: { type: String, required: false, default: 'en-US' },
142
+ },
143
+ computed: {
144
+ captionsList() {
145
+ return !this.expanded
146
+ ? 'captions-list captions-list--state-collapsed'
147
+ : 'captions-list captions-list--state-expanded'
148
+ },
149
+ cues() {
150
+ // Normal cues view
151
+ if (
152
+ typeof this.captions.cues !== 'undefined' &&
153
+ !this.paragraphView
154
+ ) {
155
+ return this.captions.cues
156
+ } else if (
157
+ typeof this.captions.cues !== 'undefined' &&
158
+ this.paragraphView
159
+ ) {
160
+ // Paragraph view
161
+ let cues = this.captions.cues
162
+ const paragraphs = []
163
+ let puncuationCount = 0
164
+
165
+ for (let i = 0; i < cues.length; i++) {
166
+ // Add the first item. Use `new VTTCue` to break the reference
167
+ if (paragraphs.length === 0) {
168
+ paragraphs.push(
169
+ new VTTCue(
170
+ cues[i].startTime,
171
+ cues[i].endTime,
172
+ cues[i].text
173
+ )
174
+ )
175
+ // Skip first element
176
+ continue
177
+ }
178
+
179
+ // Increment the count on puncuation checks
180
+ if (new RegExp(/[.?!]/).test(cues[i].text)) {
181
+ puncuationCount++
182
+ }
183
+
184
+ // Create a new paragraph every 3 sentences
185
+ if (puncuationCount > 3) {
186
+ // Find the first puncuation and include it in the slice
187
+ const breakIndex = cues[i].text.search(/[.?!]/) + 1
188
+
189
+ // Append the first part to the previous paragraph so it ends on a period
190
+ paragraphs[paragraphs.length - 1].text +=
191
+ ' ' + cues[i].text.slice(0, breakIndex)
192
+
193
+ // Use `new VTTCue` to break the reference. Otherwise the below appends will duplicate text
194
+ // Also grab from the breakIndex afterwards to get the potential next sentence
195
+ paragraphs.push(
196
+ new VTTCue(
197
+ cues[i].startTime,
198
+ cues[i].endTime,
199
+ cues[i].text.slice(breakIndex).trim()
200
+ )
201
+ )
202
+ puncuationCount = 0
203
+ } else {
204
+ // Append the cue text and update the end time
205
+ paragraphs[paragraphs.length - 1].endTime =
206
+ cues[i].endTime
207
+ paragraphs[paragraphs.length - 1].text +=
208
+ ' ' + cues[i].text
209
+ }
210
+ }
211
+
212
+ return paragraphs
213
+ } else {
214
+ // No cues found!
215
+ return []
216
+ }
217
+ },
218
+ },
219
+ data() {
220
+ return {
221
+ t,
222
+ filters,
223
+ captions: {},
224
+ captionIndex: 0,
225
+ expanded: false,
226
+ paragraphView: false,
227
+ }
228
+ },
229
+ watch: {
230
+ value: {
231
+ deep: true,
232
+ handler(captions) {
233
+ this.captions = captions
234
+ this.captionIndex = this.currentCue(this.captions)
235
+ },
236
+ },
237
+ },
238
+ methods: {
239
+ currentCue(captions) {
240
+ let currentIndex = 0
241
+
242
+ if (
243
+ typeof captions.cues !== 'undefined' &&
244
+ typeof captions.activeCues !== 'undefined' &&
245
+ captions.activeCues.length
246
+ ) {
247
+ for (let i = 0; i < captions.cues.length; i++) {
248
+ const cue = captions.cues[i]
249
+ if (captions.activeCues[0].startTime === cue.startTime) {
250
+ currentIndex = i
251
+ }
252
+ }
253
+ } else {
254
+ // If no active queues then keep the index the same
255
+ return this.captionIndex
256
+ }
257
+
258
+ // If the captions ref and index is available and the list ref is available
259
+ // Auto-scroll the list to the current caption
260
+ if (
261
+ this.$refs.captionItems &&
262
+ this.$refs.captionItems[currentIndex] &&
263
+ this.$refs.captionItems[currentIndex].$el &&
264
+ this.$refs.captionList &&
265
+ this.$refs.captionList.$el
266
+ ) {
267
+ this.$refs.captionList.$el.scrollTop =
268
+ this.$refs.captionItems[currentIndex].$el.offsetTop
269
+ }
270
+
271
+ return currentIndex
272
+ },
273
+ onCueClick(time) {
274
+ this.$emit('click:cue', time)
275
+ },
276
+ onClickToggleExpand() {
277
+ this.expanded = !this.expanded
278
+ this.$emit('click:expand', this.expanded)
279
+ },
280
+ onClickToggleParagraphView() {
281
+ this.paragraphView = !this.paragraphView
282
+ this.$emit('click:paragraph', this.paragraphView)
283
+ },
284
+ },
285
+ mounted() {
286
+ this.captions = this.value
287
+ this.captionIndex = this.currentCue(this.captions)
288
+ },
289
+ }
290
+ </script>
291
+
292
+ <style scoped>
293
+ .captions-list {
294
+ overflow-y: scroll;
295
+ }
296
+ .captions-list--state-collapsed {
297
+ max-height: 10em;
298
+ /* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
299
+ mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
300
+ linear-gradient(
301
+ 0deg,
302
+ rgba(0, 0, 0, 0) 0%,
303
+ rgba(0, 0, 0, 1) 20%,
304
+ rgba(0, 0, 0, 1) 80%,
305
+ rgba(0, 0, 0, 0) 100%
306
+ );
307
+ }
308
+ .captions-list--state-expanded {
309
+ aspect-ratio: 16 / 9;
310
+ /* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
311
+ mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
312
+ linear-gradient(
313
+ 0deg,
314
+ rgba(0, 0, 0, 0) 0%,
315
+ rgba(0, 0, 0, 1) 5%,
316
+ rgba(0, 0, 0, 1) 95%,
317
+ rgba(0, 0, 0, 0) 100%
318
+ );
319
+ }
320
+ .caption-text {
321
+ overflow: visible;
322
+ white-space: initial;
323
+ }
324
+ </style>