tangled
alpha
login
or
join now
ansxor.ca
/
markup2
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
1
pulls
pipelines
better video/audio players, i hope
12Me21
3 years ago
c717ec6b
41cd015d
+114
-138
4 changed files
expand all
collapse all
unified
split
markup.css
parse.js
render.js
runtime.js
+32
-3
markup.css
···
297
297
/** Media **/
298
298
/***********/
299
299
300
300
+
y12-audio {
301
301
+
display: contents;
302
302
+
}
303
303
+
y12-audio > audio {
304
304
+
display: block;
305
305
+
width: 100%;
306
306
+
}
307
307
+
y12-audio > a {
308
308
+
display: flex;
309
309
+
width: 100%;
310
310
+
align-items: center;
311
311
+
padding: 3px 0.5rem;
312
312
+
box-sizing: border-box;
313
313
+
height: 40px;
314
314
+
background: #555;
315
315
+
color: silver;
316
316
+
line-break: anywhere;
317
317
+
text-decoration: none;
318
318
+
}
319
319
+
y12-audio > a > span {
320
320
+
padding-left: 0.25rem;
321
321
+
}
322
322
+
300
323
.M-image-wrapper {
301
324
aspect-ratio: 16/9;
302
325
contain: strict;
···
315
338
background: black;
316
339
}
317
340
318
318
-
.Markup media-player {
341
341
+
y12-video > figure > span {
342
342
+
z-index: 1;
343
343
+
color: white;
344
344
+
overflow-y: scroll;
345
345
+
}
346
346
+
347
347
+
y12-video {
319
348
display: flex;
320
349
flex-flow: column;
321
350
max-width: 100%;
···
328
357
padding-right: 3px;
329
358
}
330
359
331
331
-
.M-audio-player, .M-video-player > .M-media-controls {
360
360
+
y12-video > .M-media-controls {
332
361
border: 2px solid var(--T-border-color);
333
362
background: slategray;
334
363
border-radius: 0 3px 3px 3px;
···
336
365
width: max-content;
337
366
}
338
367
339
339
-
.Markup media-player > * {
368
368
+
y12-video > * {
340
369
flex: none;
341
370
}
342
371
+3
-3
parse.js
···
110
110
args.alt = rargs.named.alt
111
111
// todo: improve this
112
112
if (!type) {
113
113
-
if (/[.](mp3|ogg|wav|m4a)\b/i.test(url))
113
113
+
if (/[.](mp3|ogg|wav|m4a|flac)\b/i.test(url))
114
114
type = 'audio'
115
115
-
else if (/[.](mp4|mkv|mov)\b/i.test(url))
115
115
+
else if (/[.](mp4|mkv|mov|webm)\b/i.test(url))
116
116
type = 'video'
117
117
else if (/^https?:[/][/](?:www[.])?(?:youtube.com[/]watch[?]v=|youtu[.]be[/]|youtube.com[/]shorts[/])[\w-]{11}/.test(url)) {
118
118
// todo: accept [start-end] args maybe?
···
293
293
}
294
294
const ARG_REGEX = /.*?(?=])/y
295
295
const WORD_REGEX = /[^\s`^()+=\[\]{}\\|"';:,.<>/?!*]*/y
296
296
-
const CODE_REGEX = /(?: *([-\w.+#$ ]+?) *(?![^\n]))?\n?([^]*?)(?:```|$)/y // ack
296
296
+
const CODE_REGEX = /(?: *([-\w.+#$ ]+?) *(?![^\n]))?\n?([^]*?)(?:\n?```|$)/y // ack
297
297
298
298
const parse=(text)=>{
299
299
let tree = {type: 'ROOT', content: [], prev: 'all_newline'}
+43
-98
render.js
···
121
121
error: 𐀶`<div class='error'><code>🕯error🕯</code>🕯message🕯<pre>🕯stack🕯`,
122
122
123
123
audio: function({url}) {
124
124
+
url = filter_url(url, 'audio')
124
125
let e = this()
125
125
-
let src = filter_url(url, 'audio')
126
126
-
let c2 = e.lastChild
127
127
-
let c1 = c2.previousSibling
128
128
-
let [time, save, vol, volume] = c2.childNodes
129
129
-
let [play, progress, , loop] = c1.childNodes
130
130
-
save.href = src
131
131
-
132
132
-
let audio
133
133
-
function setup() {
134
134
-
audio = document.createElement('audio')
135
135
-
audio.preload = 'none'
136
136
-
audio.src = src
137
137
-
138
138
-
time.textContent = 'loading'
139
139
-
140
140
-
volume.oninput = e=>{
141
141
-
audio.volume = +volume.value
142
142
-
}
143
143
-
function anim() {
144
144
-
time.textContent = format_time(audio.currentTime)+" / "+format_time(audio.duration)
145
145
-
progress.value = Math.round(audio.currentTime*10)/10
146
146
-
}
147
147
-
loop.onchange = e=>{ audio.loop = loop.checked }
148
148
-
audio.onpause = e=>{
149
149
-
play.textContent = "▶️"
150
150
-
}
151
151
-
audio.onpause()
152
152
-
audio.onplay = e=>{
153
153
-
play.textContent = "⏸️"
154
154
-
}
155
155
-
audio.onerror = e=>{
156
156
-
time.textContent = "Error"
157
157
-
}
158
158
-
function format_time(dur) {
159
159
-
let s = dur
160
160
-
let m = Math.floor(s / 60)
161
161
-
s = s % 60
162
162
-
return m+":"+(s+100).toFixed(1).substring(1)
163
163
-
}
164
164
-
audio.onvolumechange = e=>{
165
165
-
let volume = audio.volume
166
166
-
vol.textContent = volume ? ["🔈", "🔉", "🔊"][volume*2.99|0] : "🔇"
167
167
-
}
168
168
-
if (volume.value==1) {
169
169
-
volume.value = audio.volume
170
170
-
audio.onvolumechange()
171
171
-
} else {
172
172
-
volume.oninput()
173
173
-
}
174
174
-
audio.ondurationchange = e=>{
175
175
-
progress.max = Math.round(audio.duration*10)/10
176
176
-
time.textContent = format_time(audio.currentTime)+" / "+format_time(audio.duration)
177
177
-
}
178
178
-
audio.ontimeupdate = e=>{
179
179
-
anim()
180
180
-
}
181
181
-
progress.onchange = e=>{
182
182
-
audio.currentTime = progress.value
183
183
-
}
126
126
+
e.dataset.src = url
127
127
+
e.onclick = ev=>{
128
128
+
ev.preventDefault()
129
129
+
let e = ev.currentTarget
130
130
+
let audio = document.createElement('audio')
131
131
+
audio.controls = true
132
132
+
audio.autoplay = true
133
133
+
audio.src = e.dataset.src
134
134
+
e.replaceChildren(audio)
135
135
+
e.onclick = null
184
136
}
185
185
-
186
186
-
play.onclick = e=>{
187
187
-
if (!audio)
188
188
-
setup()
189
189
-
if (audio.paused)
190
190
-
audio.play()
191
191
-
else
192
192
-
audio.pause()
193
193
-
}
137
137
+
let link = e.firstChild
138
138
+
link.href = url
139
139
+
link.title = url
140
140
+
link.lastChild.textContent = url.replace(/.*[/]/, "…/")
194
141
return e
195
195
-
}.bind(𐀶`
196
196
-
<media-player class='M-audio-player'>
197
197
-
<div class='M-media-controls'>
198
198
-
<button>▶️</button>
199
199
-
<input type=range max=100 step=0.1 value=0>
200
200
-
🔁<input type=checkbox title=loop></input>
201
201
-
</div>
202
202
-
<div class='M-media-controls'>
203
203
-
<span class='M-media-time'>‒‒/‒‒</span>
204
204
-
<a target=_blank>💾</a>
205
205
-
<span>🔊</span>
206
206
-
<input type=range max=1 step=0.01 value=1 class='M-media-volume'>
207
207
-
</div>
208
208
-
</media-player>
209
209
-
`),
142
142
+
}.bind(𐀶`<y12-audio><a>🎵️<span></span></a></y12-audio>`),
143
143
+
210
144
video: function({url}) {
211
145
let e = this()
212
146
let media = document.createElement('video')
147
147
+
media.setAttribute('tabindex', 0)
213
148
media.preload = 'none'
214
149
media.dataset.shrink = "video"
215
150
media.src = filter_url(url, 'video')
216
151
e.firstChild.append(media)
152
152
+
217
153
let cl = e.lastChild
218
154
let [play, progress, time] = cl.childNodes
219
155
play.onclick = e=>{
220
220
-
if (media.paused) {
156
156
+
if (media.paused)
221
157
media.play()
222
222
-
//let e2 = new Event('videoclicked', {bubbles: true, cancellable: true})
223
223
-
//media.dispatchEvent(e2)
224
224
-
} else
158
158
+
else
225
159
media.pause()
226
160
e.stopPropagation()
227
161
}
162
162
+
media.onpause = e=>{
163
163
+
play.textContent = "▶️"
164
164
+
}
165
165
+
media.onplay = e=>{
166
166
+
play.textContent = "⏸️"
167
167
+
}
228
168
media.onresize = ev=>{
229
169
media.onresize = null
230
170
media.parentNode.style.aspectRatio = media.videoWidth+"/"+media.videoHeight
231
171
media.parentNode.style.height = media.videoHeight+"px"
232
172
media.parentNode.style.width = media.videoWidth+"px"
233
173
}
174
174
+
media.onerror = ev=>{
175
175
+
time.textContent = 'Error'
176
176
+
}
234
177
media.ondurationchange = e=>{
235
178
let s = media.duration
179
179
+
progress.disabled = false
180
180
+
progress.max = s
236
181
let m = Math.floor(s / 60)
237
182
s = s % 60
238
183
time.textContent = m+":"+(s+100).toFixed(2).substring(1)
239
184
}
240
185
media.ontimeupdate = e=>{
241
241
-
progress.value = media.currentTime / media.duration * 100
186
186
+
progress.value = media.currentTime
242
187
}
243
188
progress.onchange = e=>{
244
244
-
media.currentTime = progress.value/100 * media.duration
189
189
+
media.currentTime = progress.value
245
190
}
246
191
return e
247
192
}.bind(𐀶`
248
248
-
<media-player class='M-video-player'>
249
249
-
<div class='M-image-wrapper'></div>
250
250
-
<div class='M-media-controls'>
251
251
-
<button>Play</button>
252
252
-
<input type=range max=100 value=0>
253
253
-
<span>not loaded</span>
254
254
-
</div>
255
255
-
</media-player>
193
193
+
<y12-video>
194
194
+
<figure class='M-image-wrapper'></figure>
195
195
+
<div class='M-media-controls'>
196
196
+
<button>▶️</button>
197
197
+
<input type=range min=0 max=1 step=any value=0 disabled>
198
198
+
<span>not loaded</span>
199
199
+
</div>
200
200
+
</y12-video>
256
201
`),
257
202
258
203
italic: 𐀶`<i>`,
···
315
260
let e = this()
316
261
e.firstChild.textContent = url
317
262
e.firstChild.href = url
318
318
-
e.setAttribute('href', url)
263
263
+
e.dataset.href = url
319
264
return e
320
265
}.bind(𐀶`<youtube-embed><a target=_blank></a></youtube-embed>`),
321
266
+36
-34
runtime.js
···
19
19
</youtube-embed>
20
20
That way, it's still accessible if the custom element isn't installed.
21
21
**/
22
22
-
class YoutubeEmbedElement extends HTMLElement {
22
22
+
class Markup_YoutubeElement extends HTMLElement {
23
23
constructor() {
24
24
super()
25
25
this.attachShadow({mode: 'open'})
26
26
let e = this.constructor.template()
27
27
for (let elem of e.querySelectorAll("[id]"))
28
28
-
this["_"+elem.id] = elem
29
29
-
this._link.onclick = e=>{
30
30
-
e.preventDefault()
28
28
+
this["$"+elem.id] = elem
29
29
+
this.$link.onclick = ev=>{
30
30
+
ev.preventDefault()
31
31
this.show_youtube(true)
32
32
}
33
33
-
this._close.onclick = e=>{
33
33
+
this.$close.onclick = ev=>{
34
34
this.show_youtube(false)
35
35
}
36
36
this.shadowRoot.append(e)
37
37
}
38
38
show_youtube(state) {
39
39
-
this._close.hidden = !state
39
39
+
this.$close.hidden = !state
40
40
this.toggleAttribute('data-big', state)
41
41
-
if (!this._iframe == !state)
41
41
+
if (!this.$iframe == !state)
42
42
return
43
43
if (state) {
44
44
-
this._iframe = document.createElement('iframe')
44
44
+
this.$iframe = document.createElement('iframe')
45
45
let src = `https://www.youtube-nocookie.com/embed/${this._id}?autoplay=1&rel=0&modestbranding=1`
46
46
if (this._query)
47
47
src += `&${this._query}`
48
48
-
this._iframe.src = src
49
49
-
this._link.replaceWith(this._iframe)
48
48
+
this.$iframe.src = src
49
49
+
this.$link.replaceWith(this.$iframe)
50
50
} else {
51
51
-
this._iframe.replaceWith(this._link)
52
52
-
this._iframe.src = "about:blank"
53
53
-
this._iframe = null
51
51
+
this.$iframe.replaceWith(this.$link)
52
52
+
this.$iframe.src = "about:blank"
53
53
+
this.$iframe = null
54
54
}
55
55
}
56
56
connectedCallback() {
57
57
-
this.update_href(this.getAttribute('href'))
57
57
+
this.update_href(this.dataset.href)
58
58
}
59
59
disconnectedCallback() {
60
60
this._id = null
···
66
66
if (this._href == url)
67
67
return
68
68
this._href = url
69
69
-
this._title.textContent = url
70
70
-
this._author.textContent = ""
71
71
-
this._link.href = url
69
69
+
this.$title.textContent = url
70
70
+
this.$author.textContent = ""
71
71
+
this.$link.href = url
72
72
73
73
let [, id, query] = /^https?:[/][/](?:www[.])?(?:youtube.com[/]watch[?]v=|youtu[.]be[/])([\w-]{11,})([&?].*)?$/.exec(url)
74
74
if (query) {
···
85
85
// display video info
86
86
if (this._id == id)
87
87
return
88
88
-
this._link.style.backgroundImage = `url(https://i.ytimg.com/vi/${id}/mqdefault.jpg)`
88
88
+
this.$link.style.backgroundImage = `url(https://i.ytimg.com/vi/${id}/mqdefault.jpg)`
89
89
this._id = id
90
90
// only do one at a time
91
91
-
let f = YoutubeEmbedElement.requests[id]
91
91
+
let f = Markup_YoutubeElement.requests[id]
92
92
if (!f) {
93
93
// todo: cancel these when node is disconnected?
94
94
-
YoutubeEmbedElement.requests[id] = f = fetch(`https://www.youtube.com/oembed?url=https%3A//youtube.com/watch%3Fv%3D${id}&format=json`).then(x=>x.json()).catch(x=>null)
94
94
+
Markup_YoutubeElement.requests[id] = f = fetch(`https://www.youtube.com/oembed?url=https%3A//youtube.com/watch%3Fv%3D${id}&format=json`).then(x=>x.json()).catch(x=>null)
95
95
}
96
96
f.then(data=>{
97
97
if (this._id != id)
98
98
return // if the video changed
99
99
if (!data)
100
100
-
data = {title: "unknown video"}
101
101
-
this._title.textContent = data.title
102
102
-
this._author.textContent = data.author_name
100
100
+
data = {title: url, author_name: "(metadata request failed)"}
101
101
+
this.$title.textContent = data.title
102
102
+
this.$author.textContent = data.author_name
103
103
})
104
104
}
105
105
attributeChangedCallback(name, old, value) {
···
108
108
}
109
109
}
110
110
// intern these?
111
111
-
YoutubeEmbedElement.requests = {}
112
112
-
YoutubeEmbedElement.observedAttributes = ['href']
111
111
+
Markup_YoutubeElement.requests = {}
112
112
+
Markup_YoutubeElement.observedAttributes = ['data-href']
113
113
{
114
114
let template = ([html])=>{
115
115
let temp = document.createElement('template')
116
116
-
temp.innerHTML = html.replace(/\s*\n\s*/g, "")
116
116
+
temp.innerHTML = html.replace(/\s*?\n\s*/g, "")
117
117
return document.importNode.bind(document, temp.content, true)
118
118
}
119
119
-
YoutubeEmbedElement.template = template`
119
119
+
Markup_YoutubeElement.template = template`
120
120
<a target=_blank id=link>
121
121
<cite id=caption>
122
122
<span id=title></span>
···
124
124
<span id=author></span>
125
125
</cite>
126
126
</a>
127
127
-
<button hidden id=close>❌</button>
127
127
+
<button hidden id=close>❌ close</button>
128
128
<style>
129
129
:host {
130
130
-
border: 2px solid gray;
131
130
display: flex !important;
131
131
+
border: 2px solid gray;
132
132
--height: 135px;
133
133
-
flex-direction: column;
133
133
+
flex-direction: column;
134
134
}
135
135
:host([data-big]) {
136
136
--height: 270px;
137
137
}
138
138
#close {
139
139
-
width: 25px
139
139
+
/*width: 25px;*/
140
140
flex-shrink: 0;
141
141
}
142
142
iframe {
···
149
149
height: var(--height);
150
150
padding: 4px;
151
151
overflow-y: auto;
152
152
-
background-repeat: no-repeat;
153
153
-
background-size: contain;
152
152
+
background: no-repeat 0 / contain;
154
153
overflow-wrap: break-word;
155
154
white-space: pre-wrap;
156
155
flex-grow: 1;
···
162
161
font-family: sans-serif;
163
162
display: inline;
164
163
}
164
164
+
#caption > span {
165
165
+
padding: 0 0.25rem;
166
166
+
}
165
167
#caption > div {
166
168
height: 5px;
167
169
}
···
173
175
`
174
176
}
175
177
176
176
-
customElements.define('youtube-embed', YoutubeEmbedElement)
178
178
+
customElements.define('youtube-embed', Markup_YoutubeElement)