mirror of
https://github.com/iv-org/invidious
synced 2024-11-13 22:53:34 +01:00
2 lines
25 KiB
JavaScript
2 lines
25 KiB
JavaScript
class AnnotationParser{static get defaultAppearanceAttributes(){return{bgColor:16777215,bgOpacity:.8,fgColor:0,textSize:3.15}}static get attributeMap(){return{type:"tp",style:"s",x:"x",y:"y",width:"w",height:"h",sx:"sx",sy:"sy",timeStart:"ts",timeEnd:"te",text:"t",actionType:"at",actionUrl:"au",actionUrlTarget:"aut",actionSeconds:"as",bgOpacity:"bgo",bgColor:"bgc",fgColor:"fgc",textSize:"txsz"}}deserializeAnnotation(serializedAnnotation){const map=this.constructor.attributeMap;const attributes=serializedAnnotation.split(",");const annotation={};for(const attribute of attributes){const[key,value]=attribute.split("=");const mappedKey=this.getKeyByValue(map,key);let finalValue="";if(["text","actionType","actionUrl","actionUrlTarget","type","style"].indexOf(mappedKey)>-1){finalValue=decodeURIComponent(value)}else{finalValue=parseFloat(value,10)}annotation[mappedKey]=finalValue}return annotation}serializeAnnotation(annotation){const map=this.constructor.attributeMap;let serialized="";for(const key in annotation){const mappedKey=map[key];if(["text","actionType","actionUrl","actionUrlTarget"].indexOf(key)>-1&&mappedKey&&annotation.hasOwnProperty(key)){let text=encodeURIComponent(annotation[key]);serialized+=`${mappedKey}=${text},`}else if(["text","actionType","actionUrl","actionUrlTarget"].indexOf("key")===-1&&mappedKey&&annotation.hasOwnProperty(key)){serialized+=`${mappedKey}=${annotation[key]},`}}return serialized.substring(0,serialized.length-1)}deserializeAnnotationList(serializedAnnotationString){const serializedAnnotations=serializedAnnotationString.split(";");serializedAnnotations.length=serializedAnnotations.length-1;const annotations=[];for(const annotation of serializedAnnotations){annotations.push(this.deserializeAnnotation(annotation))}return annotations}serializeAnnotationList(annotations){let serialized="";for(const annotation of annotations){serialized+=this.serializeAnnotation(annotation)+";"}return serialized}xmlToDom(xml){const parser=new DOMParser;const dom=parser.parseFromString(xml,"application/xml");return dom}getAnnotationsFromXml(xml){const dom=this.xmlToDom(xml);return dom.getElementsByTagName("annotation")}parseYoutubeAnnotationList(annotationElements){const annotations=[];for(const el of annotationElements){const parsedAnnotation=this.parseYoutubeAnnotation(el);if(parsedAnnotation)annotations.push(parsedAnnotation)}return annotations}parseYoutubeAnnotation(annotationElement){const base=annotationElement;const attributes=this.getAttributesFromBase(base);if(!attributes.type||attributes.type==="pause")return null;const text=this.getTextFromBase(base);const action=this.getActionFromBase(base);const backgroundShape=this.getBackgroundShapeFromBase(base);if(!backgroundShape)return null;const timeStart=backgroundShape.timeRange.start;const timeEnd=backgroundShape.timeRange.end;if(isNaN(timeStart)||isNaN(timeEnd)||timeStart===null||timeEnd===null){return null}const appearance=this.getAppearanceFromBase(base);let annotation={type:attributes.type,x:backgroundShape.x,y:backgroundShape.y,width:backgroundShape.width,height:backgroundShape.height,timeStart:timeStart,timeEnd:timeEnd};if(attributes.style)annotation.style=attributes.style;if(text)annotation.text=text;if(action)annotation=Object.assign(action,annotation);if(appearance)annotation=Object.assign(appearance,annotation);if(backgroundShape.hasOwnProperty("sx"))annotation.sx=backgroundShape.sx;if(backgroundShape.hasOwnProperty("sy"))annotation.sy=backgroundShape.sy;return annotation}getBackgroundShapeFromBase(base){const movingRegion=base.getElementsByTagName("movingRegion")[0];if(!movingRegion)return null;const regionType=movingRegion.getAttribute("type");const regions=movingRegion.getElementsByTagName(`${regionType}Region`);const timeRange=this.extractRegionTime(regions);const shape={type:regionType,x:parseFloat(regions[0].getAttribute("x"),10),y:parseFloat(regions[0].getAttribute("y"),10),width:parseFloat(regions[0].getAttribute("w"),10),height:parseFloat(regions[0].getAttribute("h"),10),timeRange:timeRange};const sx=regions[0].getAttribute("sx");const sy=regions[0].getAttribute("sy");if(sx)shape.sx=parseFloat(sx,10);if(sy)shape.sy=parseFloat(sy,10);return shape}getAttributesFromBase(base){const attributes={};attributes.type=base.getAttribute("type");attributes.style=base.getAttribute("style");return attributes}getTextFromBase(base){const textElement=base.getElementsByTagName("TEXT")[0];if(textElement)return textElement.textContent}getActionFromBase(base){const actionElement=base.getElementsByTagName("action")[0];if(!actionElement)return null;const typeAttr=actionElement.getAttribute("type");const urlElement=actionElement.getElementsByTagName("url")[0];if(!urlElement)return null;const actionUrlTarget=urlElement.getAttribute("target");const href=urlElement.getAttribute("value");if(href.startsWith("https://www.youtube.com/")){const url=new URL(href);const srcVid=url.searchParams.get("src_vid");const toVid=url.searchParams.get("v");return this.linkOrTimestamp(url,srcVid,toVid,actionUrlTarget)}}linkOrTimestamp(url,srcVid,toVid,actionUrlTarget){if(srcVid&&toVid&&srcVid===toVid){let seconds=0;const hash=url.hash;if(hash&&hash.startsWith("#t=")){const timeString=url.hash.split("#t=")[1];seconds=this.timeStringToSeconds(timeString)}return{actionType:"time",actionSeconds:seconds}}else{return{actionType:"url",actionUrl:url.href,actionUrlTarget:actionUrlTarget}}}getAppearanceFromBase(base){const appearanceElement=base.getElementsByTagName("appearance")[0];const styles=this.constructor.defaultAppearanceAttributes;if(appearanceElement){const bgOpacity=appearanceElement.getAttribute("bgAlpha");const bgColor=appearanceElement.getAttribute("bgColor");const fgColor=appearanceElement.getAttribute("fgColor");const textSize=appearanceElement.getAttribute("textSize");if(bgOpacity)styles.bgOpacity=parseFloat(bgOpacity,10);if(bgColor)styles.bgColor=parseInt(bgColor,10);if(fgColor)styles.fgColor=parseInt(fgColor,10);if(textSize)styles.textSize=parseFloat(textSize,10)}return styles}extractRegionTime(regions){let timeStart=regions[0].getAttribute("t");timeStart=this.hmsToSeconds(timeStart);let timeEnd=regions[regions.length-1].getAttribute("t");timeEnd=this.hmsToSeconds(timeEnd);return{start:timeStart,end:timeEnd}}hmsToSeconds(hms){let p=hms.split(":");let s=0;let m=1;while(p.length>0){s+=m*parseFloat(p.pop(),10);m*=60}return s}timeStringToSeconds(time){let seconds=0;const h=time.split("h");const m=(h[1]||time).split("m");const s=(m[1]||time).split("s");if(h[0]&&h.length===2)seconds+=parseInt(h[0],10)*60*60;if(m[0]&&m.length===2)seconds+=parseInt(m[0],10)*60;if(s[0]&&s.length===2)seconds+=parseInt(s[0],10);return seconds}getKeyByValue(obj,value){for(const key in obj){if(obj.hasOwnProperty(key)){if(obj[key]===value){return key}}}}}class AnnotationRenderer{constructor(annotations,container,playerOptions,updateInterval=1e3){if(!annotations)throw new Error("Annotation objects must be provided");if(!container)throw new Error("An element to contain the annotations must be provided");if(playerOptions&&playerOptions.getVideoTime&&playerOptions.seekTo){this.playerOptions=playerOptions}else{console.info("AnnotationRenderer is running without a player. The update method will need to be called manually.")}this.annotations=annotations;this.container=container;this.annotationsContainer=document.createElement("div");this.annotationsContainer.classList.add("__cxt-ar-annotations-container__");this.annotationsContainer.setAttribute("data-layer","4");this.annotationsContainer.addEventListener("click",e=>{this.annotationClickHandler(e)});this.container.prepend(this.annotationsContainer);this.createAnnotationElements();this.updateAllAnnotationSizes();window.addEventListener("DOMContentLoaded",e=>{this.updateAllAnnotationSizes()});this.updateInterval=updateInterval;this.updateIntervalId=null}changeAnnotationData(annotations){this.stop();this.removeAnnotationElements();this.annotations=annotations;this.createAnnotationElements();this.start()}createAnnotationElements(){for(const annotation of this.annotations){const el=document.createElement("div");el.classList.add("__cxt-ar-annotation__");annotation.__element=el;el.__annotation=annotation;const closeButton=this.createCloseElement();closeButton.addEventListener("click",e=>{el.setAttribute("hidden","");el.setAttribute("data-ar-closed","");if(el.__annotation.__speechBubble){const speechBubble=el.__annotation.__speechBubble;speechBubble.style.display="none"}});el.append(closeButton);if(annotation.text){const textNode=document.createElement("span");textNode.textContent=annotation.text;el.append(textNode);el.setAttribute("data-ar-has-text","")}if(annotation.style==="speech"){const containerDimensions=this.container.getBoundingClientRect();const speechX=this.percentToPixels(containerDimensions.width,annotation.x);const speechY=this.percentToPixels(containerDimensions.height,annotation.y);const speechWidth=this.percentToPixels(containerDimensions.width,annotation.width);const speechHeight=this.percentToPixels(containerDimensions.height,annotation.height);const speechPointX=this.percentToPixels(containerDimensions.width,annotation.sx);const speechPointY=this.percentToPixels(containerDimensions.height,annotation.sy);const bubbleColor=this.getFinalAnnotationColor(annotation,false);const bubble=this.createSvgSpeechBubble(speechX,speechY,speechWidth,speechHeight,speechPointX,speechPointY,bubbleColor,annotation.__element);bubble.style.display="none";bubble.style.overflow="visible";el.style.pointerEvents="none";bubble.__annotationEl=el;annotation.__speechBubble=bubble;const path=bubble.getElementsByTagName("path")[0];path.addEventListener("mouseover",()=>{closeButton.style.display="block";closeButton.style.cursor="pointer";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,true))});path.addEventListener("mouseout",e=>{if(!e.relatedTarget.classList.contains("__cxt-ar-annotation-close__")){closeButton.style.display="none";closeButton.style.cursor="default";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,false))}});closeButton.addEventListener("mouseleave",()=>{closeButton.style.display="none";path.style.cursor="default";closeButton.style.cursor="default";path.setAttribute("fill",this.getFinalAnnotationColor(annotation,false))});el.prepend(bubble)}else if(annotation.type==="highlight"){el.style.backgroundColor="";el.style.border=`2.5px solid ${this.getFinalAnnotationColor(annotation,false)}`;if(annotation.actionType==="url")el.style.cursor="pointer"}else if(annotation.style!=="title"){el.style.backgroundColor=this.getFinalAnnotationColor(annotation);el.addEventListener("mouseenter",()=>{el.style.backgroundColor=this.getFinalAnnotationColor(annotation,true)});el.addEventListener("mouseleave",()=>{el.style.backgroundColor=this.getFinalAnnotationColor(annotation,false)});if(annotation.actionType==="url")el.style.cursor="pointer"}el.style.color=`#${this.decimalToHex(annotation.fgColor)}`;el.setAttribute("data-ar-type",annotation.type);el.setAttribute("hidden","");this.annotationsContainer.append(el)}}createCloseElement(){const svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.setAttribute("viewBox","0 0 100 100");svg.classList.add("__cxt-ar-annotation-close__");const path=document.createElementNS(svg.namespaceURI,"path");path.setAttribute("d","M25 25 L 75 75 M 75 25 L 25 75");path.setAttribute("stroke","#bbb");path.setAttribute("stroke-width",10);path.setAttribute("x",5);path.setAttribute("y",5);const circle=document.createElementNS(svg.namespaceURI,"circle");circle.setAttribute("cx",50);circle.setAttribute("cy",50);circle.setAttribute("r",50);svg.append(circle,path);return svg}createSvgSpeechBubble(x,y,width,height,pointX,pointY,color="white",element,svg){const horizontalBaseStartMultiplier=.17379070765180116;const horizontalBaseEndMultiplier=.14896346370154384;const verticalBaseStartMultiplier=.12;const verticalBaseEndMultiplier=.3;let path;if(!svg){svg=document.createElementNS("http://www.w3.org/2000/svg","svg");svg.classList.add("__cxt-ar-annotation-speech-bubble__");path=document.createElementNS("http://www.w3.org/2000/svg","path");path.setAttribute("fill",color);svg.append(path)}else{path=svg.children[0]}svg.style.position="absolute";svg.setAttribute("width","100%");svg.setAttribute("height","100%");svg.style.left="0";svg.style.top="0";let positionStart;let baseStartX=0;let baseStartY=0;let baseEndX=0;let baseEndY=0;let pointFinalX=pointX;let pointFinalY=pointY;let commentRectPath;const pospad=20;let textWidth=0;let textHeight=0;let textX=0;let textY=0;let textElement;let closeElement;if(element){textElement=element.getElementsByTagName("span")[0];closeElement=element.getElementsByClassName("__cxt-ar-annotation-close__")[0]}if(pointX>x+width-width/2&&pointY>y+height){positionStart="br";baseStartX=width-width*horizontalBaseStartMultiplier*2;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;baseStartY=height;baseEndY=height;pointFinalX=pointX-x;pointFinalY=pointY-y;element.style.height=pointY-y;commentRectPath=`L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=0}}else if(pointX<x+width-width/2&&pointY>y+height){positionStart="bl";baseStartX=width*horizontalBaseStartMultiplier;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;baseStartY=height;baseEndY=height;pointFinalX=pointX-x;pointFinalY=pointY-y;element.style.height=`${pointY-y}px`;commentRectPath=`L${width} ${height} L${width} 0 L0 0 L0 ${baseStartY} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=0}}else if(pointX>x+width-width/2&&pointY<y-pospad){positionStart="tr";baseStartX=width-width*horizontalBaseStartMultiplier*2;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;const yOffset=y-pointY;baseStartY=yOffset;baseEndY=yOffset;element.style.top=y-yOffset+"px";element.style.height=height+yOffset+"px";pointFinalX=pointX-x;pointFinalY=0;commentRectPath=`L${width} ${yOffset} L${width} ${height+yOffset} L0 ${height+yOffset} L0 ${yOffset} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=yOffset}}else if(pointX<x+width-width/2&&pointY<y){positionStart="tl";baseStartX=width*horizontalBaseStartMultiplier;baseEndX=baseStartX+width*horizontalBaseEndMultiplier;const yOffset=y-pointY;baseStartY=yOffset;baseEndY=yOffset;element.style.top=y-yOffset+"px";element.style.height=height+yOffset+"px";pointFinalX=pointX-x;pointFinalY=0;commentRectPath=`L${width} ${yOffset} L${width} ${height+yOffset} L0 ${height+yOffset} L0 ${yOffset} L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=yOffset}}else if(pointX>x+width&&pointY>y-pospad&&pointY<y+height-pospad){positionStart="r";const xOffset=pointX-(x+width);baseStartX=width;baseEndX=width;element.style.width=width+xOffset+"px";baseStartY=height*verticalBaseStartMultiplier;baseEndY=baseStartY+height*verticalBaseEndMultiplier;pointFinalX=width+xOffset;pointFinalY=pointY-y;commentRectPath=`L${baseStartX} ${height} L0 ${height} L0 0 L${baseStartX} 0 L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=0;textY=0}}else if(pointX<x&&pointY>y&&pointY<y+height){positionStart="l";const xOffset=x-pointX;baseStartX=xOffset;baseEndX=xOffset;element.style.left=x-xOffset+"px";element.style.width=width+xOffset+"px";baseStartY=height*verticalBaseStartMultiplier;baseEndY=baseStartY+height*verticalBaseEndMultiplier;pointFinalX=0;pointFinalY=pointY-y;commentRectPath=`L${baseStartX} ${height} L${width+baseStartX} ${height} L${width+baseStartX} 0 L${baseStartX} 0 L${baseStartX} ${baseStartY}`;if(textElement){textWidth=width;textHeight=height;textX=xOffset;textY=0}}else{return svg}if(textElement){textElement.style.left=textX+"px";textElement.style.top=textY+"px";textElement.style.width=textWidth+"px";textElement.style.height=textHeight+"px"}if(closeElement){const closeSize=parseFloat(this.annotationsContainer.style.getPropertyValue("--annotation-close-size"),10);if(closeSize){closeElement.style.left=textX+textWidth+closeSize/-1.8+"px";closeElement.style.top=textY+closeSize/-1.8+"px"}}const pathData=`M${baseStartX} ${baseStartY} L${pointFinalX} ${pointFinalY} L${baseEndX} ${baseEndY} ${commentRectPath}`;path.setAttribute("d",pathData);return svg}getFinalAnnotationColor(annotation,hover=false){const alphaHex=hover?230..toString(16):Math.floor(annotation.bgOpacity*255).toString(16);if(!isNaN(annotation.bgColor)){const bgColorHex=this.decimalToHex(annotation.bgColor);const backgroundColor=`#${bgColorHex}${alphaHex}`;return backgroundColor}}removeAnnotationElements(){for(const annotation of this.annotations){annotation.__element.remove()}}update(videoTime){for(const annotation of this.annotations){const el=annotation.__element;if(el.hasAttribute("data-ar-closed"))continue;const start=annotation.timeStart;const end=annotation.timeEnd;if(el.hasAttribute("hidden")&&(videoTime>=start&&videoTime<end)){el.removeAttribute("hidden");if(annotation.style==="speech"&&annotation.__speechBubble){annotation.__speechBubble.style.display="block"}}else if(!el.hasAttribute("hidden")&&(videoTime<start||videoTime>end)){el.setAttribute("hidden","");if(annotation.style==="speech"&&annotation.__speechBubble){annotation.__speechBubble.style.display="none"}}}}start(){if(!this.playerOptions)throw new Error("playerOptions must be provided to use the start method");const videoTime=this.playerOptions.getVideoTime();if(!this.updateIntervalId){this.update(videoTime);this.updateIntervalId=setInterval(()=>{const videoTime=this.playerOptions.getVideoTime();this.update(videoTime);window.dispatchEvent(new CustomEvent("__ar_renderer_start"))},this.updateInterval)}}stop(){if(!this.playerOptions)throw new Error("playerOptions must be provided to use the stop method");const videoTime=this.playerOptions.getVideoTime();if(this.updateIntervalId){this.update(videoTime);clearInterval(this.updateIntervalId);this.updateIntervalId=null;window.dispatchEvent(new CustomEvent("__ar_renderer_stop"))}}updateAnnotationTextSize(annotation,containerHeight){if(annotation.textSize){const textSize=annotation.textSize/100*containerHeight;annotation.__element.style.fontSize=`${textSize}px`}}updateTextSize(){const containerHeight=this.container.getBoundingClientRect().height;for(const annotation of this.annotations){this.updateAnnotationTextSize(annotation,containerHeight)}}updateCloseSize(containerHeight){if(!containerHeight)containerHeight=this.container.getBoundingClientRect().height;const multiplier=.0423;this.annotationsContainer.style.setProperty("--annotation-close-size",`${containerHeight*multiplier}px`)}updateAnnotationDimensions(annotations,videoWidth,videoHeight){const playerWidth=this.container.getBoundingClientRect().width;const playerHeight=this.container.getBoundingClientRect().height;const widthDivider=playerWidth/videoWidth;const heightDivider=playerHeight/videoHeight;let scaledVideoWidth=playerWidth;let scaledVideoHeight=playerHeight;if(widthDivider%1!==0||heightDivider%1!==0){if(widthDivider>heightDivider){scaledVideoWidth=playerHeight/videoHeight*videoWidth;scaledVideoHeight=playerHeight}else if(heightDivider>widthDivider){scaledVideoWidth=playerWidth;scaledVideoHeight=playerWidth/videoWidth*videoHeight}}const verticalBlackBarWidth=(playerWidth-scaledVideoWidth)/2;const horizontalBlackBarHeight=(playerHeight-scaledVideoHeight)/2;const widthOffsetPercent=verticalBlackBarWidth/playerWidth*100;const heightOffsetPercent=horizontalBlackBarHeight/playerHeight*100;const widthMultiplier=scaledVideoWidth/playerWidth;const heightMultiplier=scaledVideoHeight/playerHeight;for(const annotation of annotations){const el=annotation.__element;let ax=widthOffsetPercent+annotation.x*widthMultiplier;let ay=heightOffsetPercent+annotation.y*heightMultiplier;let aw=annotation.width*widthMultiplier;let ah=annotation.height*heightMultiplier;el.style.left=`${ax}%`;el.style.top=`${ay}%`;el.style.width=`${aw}%`;el.style.height=`${ah}%`;let horizontalPadding=scaledVideoWidth*.008;let verticalPadding=scaledVideoHeight*.008;if(annotation.style==="speech"&&annotation.text){const pel=annotation.__element.getElementsByTagName("span")[0];horizontalPadding*=2;verticalPadding*=2;pel.style.paddingLeft=horizontalPadding+"px";pel.style.paddingRight=horizontalPadding+"px";pel.style.paddingBottom=verticalPadding+"px";pel.style.paddingTop=verticalPadding+"px"}else if(annotation.style!=="speech"){el.style.paddingLeft=horizontalPadding+"px";el.style.paddingRight=horizontalPadding+"px";el.style.paddingBottom=verticalPadding+"px";el.style.paddingTop=verticalPadding+"px"}if(annotation.__speechBubble){const asx=this.percentToPixels(playerWidth,ax);const asy=this.percentToPixels(playerHeight,ay);const asw=this.percentToPixels(playerWidth,aw);const ash=this.percentToPixels(playerHeight,ah);let sx=widthOffsetPercent+annotation.sx*widthMultiplier;let sy=heightOffsetPercent+annotation.sy*heightMultiplier;sx=this.percentToPixels(playerWidth,sx);sy=this.percentToPixels(playerHeight,sy);this.createSvgSpeechBubble(asx,asy,asw,ash,sx,sy,null,annotation.__element,annotation.__speechBubble)}this.updateAnnotationTextSize(annotation,scaledVideoHeight);this.updateCloseSize(scaledVideoHeight)}}updateAllAnnotationSizes(){if(this.playerOptions&&this.playerOptions.getOriginalVideoWidth&&this.playerOptions.getOriginalVideoHeight){const videoWidth=this.playerOptions.getOriginalVideoWidth();const videoHeight=this.playerOptions.getOriginalVideoHeight();this.updateAnnotationDimensions(this.annotations,videoWidth,videoHeight)}else{const playerWidth=this.container.getBoundingClientRect().width;const playerHeight=this.container.getBoundingClientRect().height;this.updateAnnotationDimensions(this.annotations,playerWidth,playerHeight)}}hideAll(){for(const annotation of this.annotations){annotation.__element.setAttribute("hidden","")}}annotationClickHandler(e){let annotationElement=e.target;if(!annotationElement.matches(".__cxt-ar-annotation__")&&!annotationElement.closest(".__cxt-ar-annotation-close__")){annotationElement=annotationElement.closest(".__cxt-ar-annotation__");if(!annotationElement)return null}let annotationData=annotationElement.__annotation;if(!annotationElement||!annotationData)return;if(annotationData.actionType==="time"){const seconds=annotationData.actionSeconds;if(this.playerOptions){this.playerOptions.seekTo(seconds);const videoTime=this.playerOptions.getVideoTime();this.update(videoTime)}window.dispatchEvent(new CustomEvent("__ar_seek_to",{detail:{seconds:seconds}}))}else if(annotationData.actionType==="url"){const data={url:annotationData.actionUrl,target:annotationData.actionUrlTarget||"current"};const timeHash=this.extractTimeHash(new URL(data.url));if(timeHash&&timeHash.hasOwnProperty("seconds")){data.seconds=timeHash.seconds}window.dispatchEvent(new CustomEvent("__ar_annotation_click",{detail:data}))}}setUpdateInterval(ms){this.updateInterval=ms;this.stop();this.start()}decimalToHex(dec){let hex=dec.toString(16);hex="000000".substr(0,6-hex.length)+hex;return hex}extractTimeHash(url){if(!url)throw new Error("A URL must be provided");const hash=url.hash;if(hash&&hash.startsWith("#t=")){const timeString=url.hash.split("#t=")[1];const seconds=this.timeStringToSeconds(timeString);return{seconds:seconds}}else{return false}}timeStringToSeconds(time){let seconds=0;const h=time.split("h");const m=(h[1]||time).split("m");const s=(m[1]||time).split("s");if(h[0]&&h.length===2)seconds+=parseInt(h[0],10)*60*60;if(m[0]&&m.length===2)seconds+=parseInt(m[0],10)*60;if(s[0]&&s.length===2)seconds+=parseInt(s[0],10);return seconds}percentToPixels(a,b){return a*b/100}}function youtubeAnnotationsPlugin(options){if(!options.annotationXml)throw new Error("Annotation data must be provided");if(!options.videoContainer)throw new Error("A video container to overlay the data on must be provided");const player=this;const xml=options.annotationXml;const parser=new AnnotationParser;const annotationElements=parser.getAnnotationsFromXml(xml);const annotations=parser.parseYoutubeAnnotationList(annotationElements);const videoContainer=options.videoContainer;const playerOptions={getVideoTime(){return player.currentTime()},seekTo(seconds){player.currentTime(seconds)},getOriginalVideoWidth(){return player.videoWidth()},getOriginalVideoHeight(){return player.videoHeight()}};raiseControls();const renderer=new AnnotationRenderer(annotations,videoContainer,playerOptions,options.updateInterval);setupEventListeners(player,renderer);renderer.start()}function setupEventListeners(player,renderer){if(!player)throw new Error("A video player must be provided");player.on("playerresize",e=>{renderer.updateAllAnnotationSizes(renderer.annotations)});player.one("loadedmetadata",e=>{renderer.updateAllAnnotationSizes(renderer.annotations)});player.on("pause",e=>{renderer.stop()});player.on("play",e=>{renderer.start()});player.on("seeking",e=>{renderer.update()});player.on("seeked",e=>{renderer.update()})}function raiseControls(){const styles=document.createElement("style");styles.textContent=`\n\t.vjs-control-bar {\n\t\tz-index: 21;\n\t}\n\t`;document.body.append(styles)}
|