-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: vertical volume control component
- Loading branch information
1 parent
8847f98
commit 459c877
Showing
5 changed files
with
286 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import {type FlowplayerUMD} from "@flowplayer/player" | ||
import { FlowplayerUMD} from "@flowplayer/player" | ||
declare global { | ||
var flowplayer: FlowplayerUMD; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { install } from "../../shared/install" | ||
import { type Player } from "@flowplayer/player" | ||
import {CLICK, VOLUME_CHANGE} from "@flowplayer/player/core/events" | ||
import support from "../utils" | ||
import { SliderStates, makeSlider } from "./slider" | ||
|
||
|
||
export default class FlowplayerVerticalVolumeControl extends HTMLElement { | ||
|
||
constructor(player: Player) { | ||
super() | ||
this.classList.add("fp-volume-control-vertical") | ||
|
||
const volume_icon = player.createComponents(flowplayer.defaultElements.VOLUME_ICON)[0] | ||
volume_icon.addEventListener(CLICK, _ => player.toggleMute()) | ||
this.append(volume_icon) | ||
|
||
if (support().ios || support().android) return | ||
|
||
const volumeBar = this.createVolumeBar(player) | ||
makeSlider(volumeBar, {onseek: this.onVolumeBarSeek.bind(this, player)}) | ||
this.append(volumeBar) | ||
|
||
player.on(VOLUME_CHANGE, () => { | ||
const muted = (player.volume == 0) || player.muted | ||
if (muted) this.adjustVolumeSlider(player, 0, volumeBar) | ||
|
||
const ui_opt = player.opt("ui") | ||
if (!this.classList.contains(SliderStates.GRABBING) && !(muted && typeof ui_opt === "number" && (4 & ui_opt) > 0)) { | ||
this.adjustVolumeSlider(player, player.volume, volumeBar) | ||
} | ||
}) | ||
|
||
volume_icon.addEventListener("pointerenter", _ => volumeBar.style.opacity = "1") | ||
this.addEventListener("pointerleave", _ => volumeBar.style.opacity = "0") | ||
} | ||
|
||
createVolumeBar(player: Player) { | ||
const volumeBar = document.createElement("div") | ||
volumeBar.classList.add("fp-volume-vertical") | ||
volumeBar.setAttribute("tabindex", "0") | ||
volumeBar.setAttribute("role", "slider") | ||
volumeBar.setAttribute("aria-valuemin", "0") | ||
volumeBar.setAttribute("aria-valuemax", "1") | ||
volumeBar.setAttribute("aria-label", player.i18n("core.volume", "volume")) | ||
|
||
const container = document.createElement("div") | ||
container.setAttribute("aria-hidden", "true") | ||
container.classList.add("fp-volume-container") | ||
|
||
const volume = document.createElement("div") | ||
volume.classList.add("fp-volume-progress", "fp-color", "use-drag-handle") | ||
|
||
const dragger = document.createElement("div") | ||
dragger.classList.add("fp-dragger", "fp-color") | ||
|
||
|
||
volume.append(dragger) | ||
volumeBar.append(container) | ||
container.append(volume) | ||
|
||
return volumeBar | ||
} | ||
|
||
onVolumeBarSeek(player: Player, volumeBar: HTMLElement, amount: number) { | ||
player._storage.setItem("volume", (player.volume = amount / 100).toString()) | ||
this.adjustVolumeSlider(player, player.volume, volumeBar) | ||
|
||
if (amount < 0) return | ||
player.muted = false | ||
player._storage.removeItem("mute") | ||
} | ||
|
||
adjustVolumeSlider(player: Player, amount: number, volume_bar: HTMLElement) { | ||
volume_bar.setAttribute("aria-valuenow", amount.toString()) | ||
volume_bar.setAttribute("aria-valuetext", Math.round(amount * 100) + "%") | ||
|
||
const progress = this.querySelector(".fp-volume-progress") as HTMLDivElement | ||
if(!progress) return | ||
progress.style.height = player.muted ? "0" : Math.round(amount * 100) + "%" | ||
} | ||
} | ||
// handle umd installs | ||
install("flowplayer-volume-control", "flowplayer-vertical-volume-control", FlowplayerVerticalVolumeControl) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "@flowplayer/components-flowplayer-vertical-volume-control", | ||
"main": "./index.ts", | ||
"description": "A vertical volume bar", | ||
"flowplayer": { | ||
"componentName": "flowplayer-vertical-volume-control", | ||
"overridenComponent": "flowplayer-volume-control", | ||
"className": "FlowplayerVerticalVolumeControl" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import { TOUCH_START, TOUCH_MOVE, TOUCH_END, TOUCH_CANCEL, | ||
MOUSE_UP, MOUSE_DOWN, MOUSE_MOVE } from "@flowplayer/player/core/events" | ||
|
||
const PASSIVE = {passive: true} | ||
|
||
export enum SliderStates | ||
{ GRABBING = "has-grab" | ||
, TOUCHING = "has-touch" | ||
} | ||
|
||
export type SliderOpts = | ||
{ onseek?: (r: HTMLElement, n : number)=> void | ||
; onstart?: (r: HTMLElement, n : number)=> void | ||
; onend?: (r: HTMLElement, n : number)=> void | ||
; onmouse?: (r: HTMLElement, n : number)=> void | ||
; ontouch?: (r: HTMLElement, n : number)=> void | ||
; ontouchend?: (r: HTMLElement, n : number)=> void | ||
} | ||
|
||
export function makeSlider (root : HTMLElement, opts : SliderOpts) : HTMLElement { | ||
let offset = 0, max = -1, prev = -1 | ||
const id = randomElementId() | ||
const classList = root.classList | ||
root.id = id | ||
|
||
classList.remove(SliderStates.GRABBING) | ||
classList.remove(SliderStates.TOUCHING) | ||
|
||
const onseek = opts.onseek || noop | ||
, onstart = opts.onstart || noop | ||
, onend = opts.onend || noop | ||
, onmouse = opts.onmouse || noop | ||
, ontouch = opts.ontouch || noop | ||
, ontouchend = opts.ontouchend || noop | ||
|
||
// root dimensions can change | ||
function calc () { | ||
const rect = root.getBoundingClientRect() | ||
offset = rect.bottom - (parseFloat(window.getComputedStyle(root).paddingBottom)) | ||
max = rect.height - parseFloat(window.getComputedStyle(root).paddingBottom) - parseFloat(window.getComputedStyle(root).paddingTop) | ||
} | ||
|
||
function extractValue (e : MouseEvent | TouchEvent) { | ||
calc() | ||
const pos = is_touch_event(e) ? e.changedTouches[0].pageY : e.pageY | ||
let val = offset - pos | ||
if (val > max) val = max | ||
if (val < 0) val = 0 | ||
return val / max * 100 | ||
} | ||
|
||
function move (e : MouseEvent | TouchEvent) { | ||
const val = extractValue(e) | ||
if (val == prev) return | ||
onseek(root, val) | ||
prev = val | ||
} | ||
|
||
root.addEventListener(TOUCH_START, function(e) { | ||
if (!shouldFire(root, e)) return | ||
//root.touching = true | ||
classList.add(SliderStates.TOUCHING) | ||
if (!is_visible(root.parentElement)) return | ||
//root.grabbing = true | ||
classList.add(SliderStates.GRABBING) | ||
ontouch(root, extractValue(e)) | ||
onstart(root, extractValue(e)) | ||
move(e) | ||
}, PASSIVE) | ||
|
||
root.addEventListener(TOUCH_MOVE, function(e) { | ||
move(e) | ||
onmouse(root, extractValue(e)) | ||
}, PASSIVE) | ||
|
||
root.addEventListener(TOUCH_END, function(e) { | ||
// mouse-up event is emitted after touchend. Prevent mouse-up event to seek when touched. | ||
setTimeout(function () { | ||
//root.touching = false | ||
classList.remove(SliderStates.TOUCHING) | ||
}, 500) | ||
if (!shouldFire(root, e)) return | ||
//root.grabbing = false | ||
classList.remove(SliderStates.GRABBING) | ||
ontouchend(root, extractValue(e)) | ||
onend(root, extractValue(e)) | ||
max = 0 | ||
}, PASSIVE) | ||
|
||
root.addEventListener(TOUCH_CANCEL, function() { | ||
classList.remove(SliderStates.GRABBING, SliderStates.TOUCHING) | ||
max = 0 | ||
}, PASSIVE) | ||
|
||
root.addEventListener(MOUSE_DOWN, function(e) { | ||
if (classList.contains(SliderStates.TOUCHING)) return | ||
document.addEventListener(MOUSE_MOVE, move) | ||
//root.grabbing = true | ||
classList.add(SliderStates.GRABBING) | ||
onstart(root, extractValue(e)) | ||
e.preventDefault() | ||
move(e) | ||
}) | ||
|
||
root.addEventListener(MOUSE_MOVE, function(e) { | ||
if (classList.contains(SliderStates.TOUCHING)) return | ||
onmouse(root, extractValue(e)) | ||
}) | ||
|
||
|
||
// remove listener | ||
document.addEventListener(MOUSE_UP, function (e) { | ||
if (classList.contains(SliderStates.TOUCHING)) return | ||
document.removeEventListener(MOUSE_MOVE, move) | ||
if(!classList.contains(SliderStates.GRABBING)) return | ||
// root.grabbing = false | ||
classList.remove(SliderStates.GRABBING) | ||
onend(root, extractValue(e)) | ||
max = 0 | ||
}) | ||
|
||
return root | ||
} | ||
|
||
function shouldFire (root : HTMLElement, e : Event) : boolean { | ||
const target = e.target | ||
if (!target) return false | ||
return ( | ||
target && | ||
!(target as any).closest(root.id) || | ||
!is_visible(root.parentElement) || | ||
!root.classList.contains(SliderStates.TOUCHING)) | ||
} | ||
|
||
|
||
function is_visible (elem: HTMLElement | null) { | ||
if (!elem) return false | ||
const style = window.getComputedStyle(elem) | ||
return style.width !== "0" && | ||
style.height !== "0" && | ||
style.opacity !== "0" && | ||
style.display !== "none" && | ||
style.visibility !== "hidden" | ||
} | ||
|
||
function is_touch_event (e : any) : e is TouchEvent { | ||
return (typeof window.TouchEvent === "function" && e instanceof TouchEvent) | ||
} | ||
|
||
function noop () {} | ||
|
||
function randomElementId () { | ||
return Math.random().toString(36).replace(/[^a-z]+/g, "").substr(0, 5) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
export default function support () { | ||
const in_browser = typeof document !== "undefined" && typeof window !== "undefined" | ||
|
||
const UA = in_browser ? navigator.userAgent : "" | ||
, IS_IPHONE = /iP(hone|od)/i.test(UA) && !/iPad/.test(UA) && !/IEMobile/i.test(UA) | ||
, IS_ANDROID = /Android/.test(UA) && !/Firefox/.test(UA) | ||
, IS_SAFARI = /^((?!chrome|android).)*safari/i.test(UA) | ||
, IS_CHROME = (/chrome|crios/i).test(UA) && !(/opr|opera|chromium|edg|ucbrowser|googlebot/i).test(UA) | ||
, IS_FIREFOX = (/firefox|fxios/i).test(UA) && !(/seamonkey/i).test(UA) | ||
, IS_EDGE = (/edg/i).test(UA) | ||
, IS_OPERA= (/opr|opera/i).test(UA) | ||
, IS_SAMSUNG = /SamsungBrowser/.test(UA) | ||
, IS_SAMSUNG_SMART_TV = IS_SAMSUNG && /SMART-TV/.test(UA) | ||
|
||
const self = | ||
{ controls : !IS_IPHONE | ||
, video : function (type : string) {return in_browser && document.createElement("video").canPlayType(type)} | ||
, lang : in_browser && window.navigator.language | ||
, android : IS_ANDROID | ||
, iphone : IS_IPHONE | ||
, safari : IS_SAFARI | ||
, edge : IS_EDGE | ||
, opera : IS_OPERA | ||
, chrome : IS_CHROME | ||
, firefox : IS_FIREFOX | ||
, ios : in_browser && (/iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream) | ||
, samsung : IS_SAMSUNG | ||
, samsung_tv : IS_SAMSUNG && IS_SAMSUNG_SMART_TV | ||
, touch : "ontouchstart" in window | ||
, tizen : "tizen" in window | ||
, webOS : "webos" in window | ||
} | ||
return self | ||
} | ||
|
||
|
||
|