vidply 1.0.5 → 1.0.7
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/LICENSE +22 -22
- package/README.md +608 -593
- package/dist/vidply.css +2422 -1807
- package/dist/vidply.esm.js +1480 -93
- package/dist/vidply.esm.js.map +3 -3
- package/dist/vidply.esm.min.js +3 -3
- package/dist/vidply.esm.min.meta.json +48 -25
- package/dist/vidply.js +1480 -93
- package/dist/vidply.js.map +3 -3
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +3 -3
- package/dist/vidply.min.meta.json +48 -25
- package/package.json +2 -2
- package/src/controls/CaptionManager.js +278 -248
- package/src/controls/ControlBar.js +2033 -2026
- package/src/controls/KeyboardManager.js +233 -233
- package/src/controls/SettingsDialog.js +417 -417
- package/src/controls/TranscriptManager.js +1803 -728
- package/src/core/Player.js +1616 -1134
- package/src/i18n/i18n.js +66 -66
- package/src/i18n/translations.js +616 -561
- package/src/icons/Icons.js +187 -183
- package/src/index.js +95 -95
- package/src/renderers/HLSRenderer.js +302 -302
- package/src/renderers/HTML5Renderer.js +298 -298
- package/src/renderers/VimeoRenderer.js +257 -257
- package/src/renderers/YouTubeRenderer.js +274 -274
- package/src/styles/vidply.css +2422 -1807
- package/src/utils/DOMUtils.js +154 -154
- package/src/utils/EventEmitter.js +53 -53
- package/src/utils/StorageManager.js +156 -0
- package/src/utils/TimeUtils.js +87 -87
package/src/utils/DOMUtils.js
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DOM manipulation utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const DOMUtils = {
|
|
6
|
-
createElement(tag, options = {}) {
|
|
7
|
-
const element = document.createElement(tag);
|
|
8
|
-
|
|
9
|
-
if (options.className) {
|
|
10
|
-
element.className = options.className;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (options.attributes) {
|
|
14
|
-
Object.entries(options.attributes).forEach(([key, value]) => {
|
|
15
|
-
element.setAttribute(key, value);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (options.innerHTML) {
|
|
20
|
-
element.innerHTML = options.innerHTML;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (options.textContent) {
|
|
24
|
-
element.textContent = options.textContent;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (options.style) {
|
|
28
|
-
Object.assign(element.style, options.style);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (options.children) {
|
|
32
|
-
options.children.forEach(child => {
|
|
33
|
-
if (child) element.appendChild(child);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return element;
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
addClass(element, className) {
|
|
41
|
-
if (element && className) {
|
|
42
|
-
element.classList.add(className);
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
removeClass(element, className) {
|
|
47
|
-
if (element && className) {
|
|
48
|
-
element.classList.remove(className);
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
toggleClass(element, className) {
|
|
53
|
-
if (element && className) {
|
|
54
|
-
element.classList.toggle(className);
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
hasClass(element, className) {
|
|
59
|
-
return element && element.classList.contains(className);
|
|
60
|
-
},
|
|
61
|
-
|
|
62
|
-
show(element) {
|
|
63
|
-
if (element) {
|
|
64
|
-
element.style.display = '';
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
hide(element) {
|
|
69
|
-
if (element) {
|
|
70
|
-
element.style.display = 'none';
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
fadeIn(element, duration = 300) {
|
|
75
|
-
if (!element) return;
|
|
76
|
-
|
|
77
|
-
element.style.opacity = '0';
|
|
78
|
-
element.style.display = '';
|
|
79
|
-
|
|
80
|
-
let start = null;
|
|
81
|
-
const animate = (timestamp) => {
|
|
82
|
-
if (!start) start = timestamp;
|
|
83
|
-
const progress = timestamp - start;
|
|
84
|
-
const opacity = Math.min(progress / duration, 1);
|
|
85
|
-
|
|
86
|
-
element.style.opacity = opacity;
|
|
87
|
-
|
|
88
|
-
if (progress < duration) {
|
|
89
|
-
requestAnimationFrame(animate);
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
requestAnimationFrame(animate);
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
fadeOut(element, duration = 300) {
|
|
97
|
-
if (!element) return;
|
|
98
|
-
|
|
99
|
-
const startOpacity = parseFloat(getComputedStyle(element).opacity) || 1;
|
|
100
|
-
let start = null;
|
|
101
|
-
|
|
102
|
-
const animate = (timestamp) => {
|
|
103
|
-
if (!start) start = timestamp;
|
|
104
|
-
const progress = timestamp - start;
|
|
105
|
-
const opacity = Math.max(startOpacity - (progress / duration), 0);
|
|
106
|
-
|
|
107
|
-
element.style.opacity = opacity;
|
|
108
|
-
|
|
109
|
-
if (progress < duration) {
|
|
110
|
-
requestAnimationFrame(animate);
|
|
111
|
-
} else {
|
|
112
|
-
element.style.display = 'none';
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
requestAnimationFrame(animate);
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
offset(element) {
|
|
120
|
-
if (!element) return { top: 0, left: 0 };
|
|
121
|
-
|
|
122
|
-
const rect = element.getBoundingClientRect();
|
|
123
|
-
return {
|
|
124
|
-
top: rect.top + window.pageYOffset,
|
|
125
|
-
left: rect.left + window.pageXOffset,
|
|
126
|
-
width: rect.width,
|
|
127
|
-
height: rect.height
|
|
128
|
-
};
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
escapeHTML(str) {
|
|
132
|
-
const div = document.createElement('div');
|
|
133
|
-
div.textContent = str;
|
|
134
|
-
return div.innerHTML;
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
sanitizeHTML(html) {
|
|
138
|
-
// Basic HTML sanitization - allow safe tags for VTT captions
|
|
139
|
-
// Since we control the HTML (from VTT parsing), we can safely allow these tags
|
|
140
|
-
const temp = document.createElement('div');
|
|
141
|
-
|
|
142
|
-
// Strip out any potentially dangerous tags/attributes
|
|
143
|
-
// Allow: strong, em, u, span, b, i with class and data-voice attributes
|
|
144
|
-
const safeHtml = html
|
|
145
|
-
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
146
|
-
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
|
147
|
-
.replace(/on\w+\s*=/gi, '') // Remove event handlers
|
|
148
|
-
.replace(/javascript:/gi, ''); // Remove javascript: protocol
|
|
149
|
-
|
|
150
|
-
temp.innerHTML = safeHtml;
|
|
151
|
-
return temp.innerHTML;
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
|
|
1
|
+
/**
|
|
2
|
+
* DOM manipulation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const DOMUtils = {
|
|
6
|
+
createElement(tag, options = {}) {
|
|
7
|
+
const element = document.createElement(tag);
|
|
8
|
+
|
|
9
|
+
if (options.className) {
|
|
10
|
+
element.className = options.className;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (options.attributes) {
|
|
14
|
+
Object.entries(options.attributes).forEach(([key, value]) => {
|
|
15
|
+
element.setAttribute(key, value);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (options.innerHTML) {
|
|
20
|
+
element.innerHTML = options.innerHTML;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (options.textContent) {
|
|
24
|
+
element.textContent = options.textContent;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (options.style) {
|
|
28
|
+
Object.assign(element.style, options.style);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (options.children) {
|
|
32
|
+
options.children.forEach(child => {
|
|
33
|
+
if (child) element.appendChild(child);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return element;
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
addClass(element, className) {
|
|
41
|
+
if (element && className) {
|
|
42
|
+
element.classList.add(className);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
removeClass(element, className) {
|
|
47
|
+
if (element && className) {
|
|
48
|
+
element.classList.remove(className);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
toggleClass(element, className) {
|
|
53
|
+
if (element && className) {
|
|
54
|
+
element.classList.toggle(className);
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
hasClass(element, className) {
|
|
59
|
+
return element && element.classList.contains(className);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
show(element) {
|
|
63
|
+
if (element) {
|
|
64
|
+
element.style.display = '';
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
hide(element) {
|
|
69
|
+
if (element) {
|
|
70
|
+
element.style.display = 'none';
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
fadeIn(element, duration = 300) {
|
|
75
|
+
if (!element) return;
|
|
76
|
+
|
|
77
|
+
element.style.opacity = '0';
|
|
78
|
+
element.style.display = '';
|
|
79
|
+
|
|
80
|
+
let start = null;
|
|
81
|
+
const animate = (timestamp) => {
|
|
82
|
+
if (!start) start = timestamp;
|
|
83
|
+
const progress = timestamp - start;
|
|
84
|
+
const opacity = Math.min(progress / duration, 1);
|
|
85
|
+
|
|
86
|
+
element.style.opacity = opacity;
|
|
87
|
+
|
|
88
|
+
if (progress < duration) {
|
|
89
|
+
requestAnimationFrame(animate);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
requestAnimationFrame(animate);
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
fadeOut(element, duration = 300) {
|
|
97
|
+
if (!element) return;
|
|
98
|
+
|
|
99
|
+
const startOpacity = parseFloat(getComputedStyle(element).opacity) || 1;
|
|
100
|
+
let start = null;
|
|
101
|
+
|
|
102
|
+
const animate = (timestamp) => {
|
|
103
|
+
if (!start) start = timestamp;
|
|
104
|
+
const progress = timestamp - start;
|
|
105
|
+
const opacity = Math.max(startOpacity - (progress / duration), 0);
|
|
106
|
+
|
|
107
|
+
element.style.opacity = opacity;
|
|
108
|
+
|
|
109
|
+
if (progress < duration) {
|
|
110
|
+
requestAnimationFrame(animate);
|
|
111
|
+
} else {
|
|
112
|
+
element.style.display = 'none';
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
requestAnimationFrame(animate);
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
offset(element) {
|
|
120
|
+
if (!element) return { top: 0, left: 0 };
|
|
121
|
+
|
|
122
|
+
const rect = element.getBoundingClientRect();
|
|
123
|
+
return {
|
|
124
|
+
top: rect.top + window.pageYOffset,
|
|
125
|
+
left: rect.left + window.pageXOffset,
|
|
126
|
+
width: rect.width,
|
|
127
|
+
height: rect.height
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
escapeHTML(str) {
|
|
132
|
+
const div = document.createElement('div');
|
|
133
|
+
div.textContent = str;
|
|
134
|
+
return div.innerHTML;
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
sanitizeHTML(html) {
|
|
138
|
+
// Basic HTML sanitization - allow safe tags for VTT captions
|
|
139
|
+
// Since we control the HTML (from VTT parsing), we can safely allow these tags
|
|
140
|
+
const temp = document.createElement('div');
|
|
141
|
+
|
|
142
|
+
// Strip out any potentially dangerous tags/attributes
|
|
143
|
+
// Allow: strong, em, u, span, b, i with class and data-voice attributes
|
|
144
|
+
const safeHtml = html
|
|
145
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
146
|
+
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
|
147
|
+
.replace(/on\w+\s*=/gi, '') // Remove event handlers
|
|
148
|
+
.replace(/javascript:/gi, ''); // Remove javascript: protocol
|
|
149
|
+
|
|
150
|
+
temp.innerHTML = safeHtml;
|
|
151
|
+
return temp.innerHTML;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple EventEmitter implementation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export class EventEmitter {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.events = {};
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
on(event, listener) {
|
|
11
|
-
if (!this.events[event]) {
|
|
12
|
-
this.events[event] = [];
|
|
13
|
-
}
|
|
14
|
-
this.events[event].push(listener);
|
|
15
|
-
return this;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
once(event, listener) {
|
|
19
|
-
const onceListener = (...args) => {
|
|
20
|
-
listener(...args);
|
|
21
|
-
this.off(event, onceListener);
|
|
22
|
-
};
|
|
23
|
-
return this.on(event, onceListener);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
off(event, listener) {
|
|
27
|
-
if (!this.events[event]) return this;
|
|
28
|
-
|
|
29
|
-
if (!listener) {
|
|
30
|
-
delete this.events[event];
|
|
31
|
-
} else {
|
|
32
|
-
this.events[event] = this.events[event].filter(l => l !== listener);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
emit(event, ...args) {
|
|
39
|
-
if (!this.events[event]) return this;
|
|
40
|
-
|
|
41
|
-
this.events[event].forEach(listener => {
|
|
42
|
-
listener(...args);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
return this;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
removeAllListeners() {
|
|
49
|
-
this.events = {};
|
|
50
|
-
return this;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Simple EventEmitter implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class EventEmitter {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.events = {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
on(event, listener) {
|
|
11
|
+
if (!this.events[event]) {
|
|
12
|
+
this.events[event] = [];
|
|
13
|
+
}
|
|
14
|
+
this.events[event].push(listener);
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
once(event, listener) {
|
|
19
|
+
const onceListener = (...args) => {
|
|
20
|
+
listener(...args);
|
|
21
|
+
this.off(event, onceListener);
|
|
22
|
+
};
|
|
23
|
+
return this.on(event, onceListener);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
off(event, listener) {
|
|
27
|
+
if (!this.events[event]) return this;
|
|
28
|
+
|
|
29
|
+
if (!listener) {
|
|
30
|
+
delete this.events[event];
|
|
31
|
+
} else {
|
|
32
|
+
this.events[event] = this.events[event].filter(l => l !== listener);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
emit(event, ...args) {
|
|
39
|
+
if (!this.events[event]) return this;
|
|
40
|
+
|
|
41
|
+
this.events[event].forEach(listener => {
|
|
42
|
+
listener(...args);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
removeAllListeners() {
|
|
49
|
+
this.events = {};
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StorageManager - Handles persistent storage of user preferences
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class StorageManager {
|
|
6
|
+
constructor(namespace = 'vidply') {
|
|
7
|
+
this.namespace = namespace;
|
|
8
|
+
this.storage = this.isStorageAvailable() ? localStorage : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if localStorage is available
|
|
13
|
+
*/
|
|
14
|
+
isStorageAvailable() {
|
|
15
|
+
try {
|
|
16
|
+
const test = '__storage_test__';
|
|
17
|
+
localStorage.setItem(test, test);
|
|
18
|
+
localStorage.removeItem(test);
|
|
19
|
+
return true;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get a namespaced key
|
|
27
|
+
*/
|
|
28
|
+
getKey(key) {
|
|
29
|
+
return `${this.namespace}_${key}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Save a value to storage
|
|
34
|
+
*/
|
|
35
|
+
set(key, value) {
|
|
36
|
+
if (!this.storage) return false;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const namespacedKey = this.getKey(key);
|
|
40
|
+
this.storage.setItem(namespacedKey, JSON.stringify(value));
|
|
41
|
+
return true;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn('Failed to save to localStorage:', e);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get a value from storage
|
|
50
|
+
*/
|
|
51
|
+
get(key, defaultValue = null) {
|
|
52
|
+
if (!this.storage) return defaultValue;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const namespacedKey = this.getKey(key);
|
|
56
|
+
const value = this.storage.getItem(namespacedKey);
|
|
57
|
+
return value ? JSON.parse(value) : defaultValue;
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.warn('Failed to read from localStorage:', e);
|
|
60
|
+
return defaultValue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Remove a value from storage
|
|
66
|
+
*/
|
|
67
|
+
remove(key) {
|
|
68
|
+
if (!this.storage) return false;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const namespacedKey = this.getKey(key);
|
|
72
|
+
this.storage.removeItem(namespacedKey);
|
|
73
|
+
return true;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.warn('Failed to remove from localStorage:', e);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Clear all namespaced values
|
|
82
|
+
*/
|
|
83
|
+
clear() {
|
|
84
|
+
if (!this.storage) return false;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const keys = Object.keys(this.storage);
|
|
88
|
+
keys.forEach(key => {
|
|
89
|
+
if (key.startsWith(this.namespace)) {
|
|
90
|
+
this.storage.removeItem(key);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return true;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.warn('Failed to clear localStorage:', e);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Save transcript preferences
|
|
102
|
+
*/
|
|
103
|
+
saveTranscriptPreferences(preferences) {
|
|
104
|
+
return this.set('transcript_preferences', preferences);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get transcript preferences
|
|
109
|
+
*/
|
|
110
|
+
getTranscriptPreferences() {
|
|
111
|
+
return this.get('transcript_preferences', null);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Save caption preferences
|
|
116
|
+
*/
|
|
117
|
+
saveCaptionPreferences(preferences) {
|
|
118
|
+
return this.set('caption_preferences', preferences);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get caption preferences
|
|
123
|
+
*/
|
|
124
|
+
getCaptionPreferences() {
|
|
125
|
+
return this.get('caption_preferences', null);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Save player preferences (volume, speed, etc.)
|
|
130
|
+
*/
|
|
131
|
+
savePlayerPreferences(preferences) {
|
|
132
|
+
return this.set('player_preferences', preferences);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get player preferences
|
|
137
|
+
*/
|
|
138
|
+
getPlayerPreferences() {
|
|
139
|
+
return this.get('player_preferences', null);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Save sign language preferences (position and size)
|
|
144
|
+
*/
|
|
145
|
+
saveSignLanguagePreferences(preferences) {
|
|
146
|
+
return this.set('sign_language_preferences', preferences);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get sign language preferences
|
|
151
|
+
*/
|
|
152
|
+
getSignLanguagePreferences() {
|
|
153
|
+
return this.get('sign_language_preferences', null);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|