Eduard Kuzmenko
3 years ago
19 changed files with 591 additions and 380 deletions
@ -0,0 +1,180 @@
@@ -0,0 +1,180 @@
|
||||
/* |
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
import { GrabEvent } from "../helpers/dom/attachGrabListeners"; |
||||
import appMediaPlaybackController from "./appMediaPlaybackController"; |
||||
import RangeSelector from "./rangeSelector"; |
||||
|
||||
export default class MediaProgressLine extends RangeSelector { |
||||
protected filledLoad: HTMLDivElement; |
||||
|
||||
protected progressRAF = 0; |
||||
|
||||
protected media: HTMLMediaElement; |
||||
protected streamable: boolean; |
||||
|
||||
constructor(media?: HTMLAudioElement | HTMLVideoElement, streamable?: boolean, withTransition?: boolean, useTransform?: boolean) { |
||||
super({ |
||||
step: 1000 / 60 / 1000, |
||||
min: 0, |
||||
max: 1, |
||||
withTransition, |
||||
useTransform |
||||
}, 0); |
||||
|
||||
if(media) { |
||||
this.setMedia(media, streamable); |
||||
} |
||||
} |
||||
|
||||
public setMedia(media: HTMLMediaElement, streamable = false) { |
||||
if(this.media) { |
||||
this.removeListeners(); |
||||
} |
||||
|
||||
if(streamable && !this.filledLoad) { |
||||
this.filledLoad = document.createElement('div'); |
||||
this.filledLoad.classList.add('progress-line__filled', 'progress-line__loaded'); |
||||
this.container.prepend(this.filledLoad); |
||||
//this.setLoadProgress();
|
||||
} else if(this.filledLoad) { |
||||
this.filledLoad.classList.toggle('hide', !streamable); |
||||
} |
||||
|
||||
this.media = media; |
||||
this.streamable = streamable; |
||||
if(!media.paused || media.currentTime > 0) { |
||||
this.onPlay(); |
||||
} |
||||
|
||||
let wasPlaying = false; |
||||
this.setSeekMax(); |
||||
this.setListeners(); |
||||
this.setHandlers({ |
||||
onMouseDown: () => { |
||||
wasPlaying = !this.media.paused; |
||||
wasPlaying && this.media.pause(); |
||||
}, |
||||
|
||||
onMouseUp: (e) => { |
||||
// cancelEvent(e.event);
|
||||
wasPlaying && this.media.play(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
protected onLoadedData = () => { |
||||
this.max = this.media.duration; |
||||
this.seek.setAttribute('max', '' + this.max); |
||||
}; |
||||
|
||||
protected onEnded = () => { |
||||
this.setProgress(); |
||||
}; |
||||
|
||||
protected onPlay = () => { |
||||
let r = () => { |
||||
this.setProgress(); |
||||
|
||||
this.progressRAF = this.media.paused ? 0 : window.requestAnimationFrame(r); |
||||
}; |
||||
|
||||
if(this.progressRAF) { |
||||
window.cancelAnimationFrame(this.progressRAF); |
||||
} |
||||
|
||||
if(this.streamable) { |
||||
this.setLoadProgress(); |
||||
} |
||||
|
||||
this.progressRAF = window.requestAnimationFrame(r); |
||||
}; |
||||
|
||||
protected onTimeUpdate = () => { |
||||
if(this.media.paused) { |
||||
this.setProgress(); |
||||
|
||||
if(this.streamable) { |
||||
this.setLoadProgress(); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
protected onProgress = (e: Event) => { |
||||
this.setLoadProgress(); |
||||
}; |
||||
|
||||
protected scrub(e: GrabEvent) { |
||||
const scrubTime = super.scrub(e); |
||||
this.media.currentTime = scrubTime; |
||||
return scrubTime; |
||||
} |
||||
|
||||
protected setLoadProgress() { |
||||
if(appMediaPlaybackController.isSafariBuffering(this.media)) return; |
||||
const buf = this.media.buffered; |
||||
const numRanges = buf.length; |
||||
|
||||
const currentTime = this.media.currentTime; |
||||
let nearestStart = 0, end = 0; |
||||
for(let i = 0; i < numRanges; ++i) { |
||||
const start = buf.start(i); |
||||
if(currentTime >= start && start >= nearestStart) { |
||||
nearestStart = start; |
||||
end = buf.end(i); |
||||
} |
||||
|
||||
//console.log('onProgress range:', i, buf.start(i), buf.end(i), this.media);
|
||||
} |
||||
|
||||
//console.log('onProgress correct range:', nearestStart, end, this.media);
|
||||
|
||||
const percents = this.media.duration ? end / this.media.duration : 0; |
||||
this.filledLoad.style.width = (percents * 100) + '%'; |
||||
//this.filledLoad.style.transform = 'scaleX(' + percents + ')';
|
||||
} |
||||
|
||||
protected setSeekMax() { |
||||
this.max = this.media.duration || 0; |
||||
if(this.max > 0) { |
||||
this.onLoadedData(); |
||||
} else { |
||||
this.media.addEventListener('loadeddata', this.onLoadedData); |
||||
} |
||||
} |
||||
|
||||
public setProgress() { |
||||
if(appMediaPlaybackController.isSafariBuffering(this.media)) return; |
||||
const currentTime = this.media.currentTime; |
||||
|
||||
super.setProgress(currentTime); |
||||
} |
||||
|
||||
public setListeners() { |
||||
super.setListeners(); |
||||
this.media.addEventListener('ended', this.onEnded); |
||||
this.media.addEventListener('play', this.onPlay); |
||||
this.media.addEventListener('timeupdate', this.onTimeUpdate); |
||||
this.streamable && this.media.addEventListener('progress', this.onProgress); |
||||
} |
||||
|
||||
public removeListeners() { |
||||
super.removeListeners(); |
||||
|
||||
if(this.media) { |
||||
this.media.removeEventListener('loadeddata', this.onLoadedData); |
||||
this.media.removeEventListener('ended', this.onEnded); |
||||
this.media.removeEventListener('play', this.onPlay); |
||||
this.media.removeEventListener('timeupdate', this.onTimeUpdate); |
||||
this.streamable && this.media.removeEventListener('progress', this.onProgress); |
||||
} |
||||
|
||||
if(this.progressRAF) { |
||||
window.cancelAnimationFrame(this.progressRAF); |
||||
this.progressRAF = 0; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,84 @@
@@ -0,0 +1,84 @@
|
||||
/* |
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/ |
||||
|
||||
import cancelEvent from "../helpers/dom/cancelEvent"; |
||||
import { attachClickEvent } from "../helpers/dom/clickEvent"; |
||||
import ListenerSetter from "../helpers/listenerSetter"; |
||||
import rootScope from "../lib/rootScope"; |
||||
import appMediaPlaybackController from "./appMediaPlaybackController"; |
||||
import RangeSelector from "./rangeSelector"; |
||||
|
||||
export default class VolumeSelector extends RangeSelector { |
||||
private static ICONS = ['volume_off', 'volume_mute', 'volume_down', 'volume_up']; |
||||
public btn: HTMLElement; |
||||
protected icon: HTMLSpanElement; |
||||
|
||||
constructor(protected listenerSetter: ListenerSetter, protected vertical = false) { |
||||
super({ |
||||
step: 0.01, |
||||
min: 0, |
||||
max: 1, |
||||
vertical |
||||
}, 1); |
||||
|
||||
this.setListeners(); |
||||
this.setHandlers({ |
||||
onScrub: currentTime => { |
||||
const value = Math.max(Math.min(currentTime, 1), 0); |
||||
|
||||
//console.log('volume scrub:', currentTime, value);
|
||||
|
||||
appMediaPlaybackController.muted = false; |
||||
appMediaPlaybackController.volume = value; |
||||
}, |
||||
|
||||
/* onMouseUp: (e) => { |
||||
cancelEvent(e.event); |
||||
} */ |
||||
}); |
||||
|
||||
const className = 'player-volume'; |
||||
const btn = this.btn = document.createElement('div'); |
||||
btn.classList.add('btn-icon', className); |
||||
const icon = this.icon = document.createElement('span'); |
||||
icon.classList.add(className + '__icon'); |
||||
|
||||
btn.append(icon, this.container); |
||||
|
||||
attachClickEvent(icon, this.onMuteClick, {listenerSetter: this.listenerSetter}); |
||||
this.listenerSetter.add(rootScope)('media_playback_params', this.setVolume); |
||||
|
||||
this.setVolume(); |
||||
} |
||||
|
||||
private onMuteClick = (e?: Event) => { |
||||
e && cancelEvent(e); |
||||
appMediaPlaybackController.muted = !appMediaPlaybackController.muted; |
||||
}; |
||||
|
||||
private setVolume = () => { |
||||
// const volume = video.volume;
|
||||
const {volume, muted} = appMediaPlaybackController; |
||||
let d: string; |
||||
let iconIndex: number; |
||||
if(!volume || muted) { |
||||
iconIndex = 0; |
||||
} else if(volume > .5) { |
||||
iconIndex = 3; |
||||
} else if(volume > 0 && volume < .25) { |
||||
iconIndex = 1; |
||||
} else { |
||||
iconIndex = 2; |
||||
} |
||||
|
||||
VolumeSelector.ICONS.forEach(icon => this.icon.classList.remove('tgico-' + icon)); |
||||
this.icon.classList.add('tgico-' + VolumeSelector.ICONS[iconIndex]); |
||||
|
||||
if(!this.mousedown) { |
||||
this.setProgress(muted ? 0 : volume); |
||||
} |
||||
}; |
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
export default function createVideo(options: { |
||||
pip?: boolean |
||||
} = {}) { |
||||
const video = document.createElement('video'); |
||||
if(!options.pip) video.disablePictureInPicture = true; |
||||
video.setAttribute('playsinline', 'true'); |
||||
return video; |
||||
} |
Loading…
Reference in new issue