@mindedge/vuetify-player 0.2.0 → 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
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@mindedge/vuetify-player",
3
- "version": "0.2.0",
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
- "test:unit": "vue-cli-service test:unit",
8
+ "test": "vue-cli-service test:unit",
9
9
  "lint": "vue-cli-service lint",
10
10
  "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"",
11
11
  "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore ."
@@ -1,32 +1,129 @@
1
1
  <template>
2
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>
3
57
  <v-card-text>
4
- <v-list ref="captionList" class="captions-list">
58
+ <v-list ref="captionList" :class="captionsList">
5
59
  <v-list-item-group v-model="captionIndex">
6
60
  <v-list-item
7
61
  ref="captionItems"
8
- v-for="(cue, index) in captions.cues"
62
+ v-for="(cue, index) in cues"
9
63
  :key="index"
64
+ :two-line="expanded"
10
65
  @click="onCueClick(cue.startTime)"
11
66
  >
12
- <v-list-item-icon>
13
- <v-icon
14
- >{{
15
- index === captionIndex
16
- ? 'mdi-arrow-right-drop-circle-outline'
17
- : 'mdi-checkbox-blank-circle-outline'
18
- }}
19
- </v-icon>
20
- </v-list-item-icon>
21
- <v-list-item-content
22
- v-html="cue.text"
23
- ></v-list-item-content>
24
- <v-list-item-action>
25
- <span aria-hidden="true">
26
- {{ filters.playerShortDuration(cue.startTime) }}
27
- - {{ filters.playerShortDuration(cue.endTime) }}
28
- </span>
29
- </v-list-item-action>
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>
30
127
  </v-list-item>
31
128
  </v-list-item-group>
32
129
  </v-list>
@@ -36,17 +133,97 @@
36
133
 
37
134
  <script>
38
135
  import filters from '../filters'
136
+ import { t } from '../../i18n/i18n'
39
137
 
40
138
  export default {
41
139
  props: {
42
- value: { type: Object, required: true },
140
+ value: { type: [Object, Array], required: true },
43
141
  language: { type: String, required: false, default: 'en-US' },
44
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
+ },
45
219
  data() {
46
220
  return {
221
+ t,
47
222
  filters,
48
223
  captions: {},
49
224
  captionIndex: 0,
225
+ expanded: false,
226
+ paragraphView: false,
50
227
  }
51
228
  },
52
229
  watch: {
@@ -59,21 +236,14 @@ export default {
59
236
  },
60
237
  },
61
238
  methods: {
62
- cueKey(cue) {
63
- const str =
64
- cue.language +
65
- cue.startTime.toString() +
66
- cue.endTime.toString() +
67
- cue.text
68
- return str.split('').reduce(function (a, b) {
69
- a = (a << 5) - a + b.charCodeAt(0)
70
- return a & a
71
- }, 0)
72
- },
73
239
  currentCue(captions) {
74
240
  let currentIndex = 0
75
241
 
76
- if (captions.activeCues && captions.activeCues.length) {
242
+ if (
243
+ typeof captions.cues !== 'undefined' &&
244
+ typeof captions.activeCues !== 'undefined' &&
245
+ captions.activeCues.length
246
+ ) {
77
247
  for (let i = 0; i < captions.cues.length; i++) {
78
248
  const cue = captions.cues[i]
79
249
  if (captions.activeCues[0].startTime === cue.startTime) {
@@ -103,6 +273,14 @@ export default {
103
273
  onCueClick(time) {
104
274
  this.$emit('click:cue', time)
105
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
+ },
106
284
  },
107
285
  mounted() {
108
286
  this.captions = this.value
@@ -113,8 +291,10 @@ export default {
113
291
 
114
292
  <style scoped>
115
293
  .captions-list {
116
- max-height: 10em;
117
294
  overflow-y: scroll;
295
+ }
296
+ .captions-list--state-collapsed {
297
+ max-height: 10em;
118
298
  /* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
119
299
  mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
120
300
  linear-gradient(
@@ -125,4 +305,20 @@ export default {
125
305
  rgba(0, 0, 0, 0) 100%
126
306
  );
127
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
+ }
128
324
  </style>