Update app.py
Browse files
app.py
CHANGED
|
@@ -708,40 +708,133 @@ if(S.playing)play();else stop();
|
|
| 708 |
}}
|
| 709 |
|
| 710 |
function play(){{
|
| 711 |
-
|
| 712 |
-
var
|
| 713 |
-
|
| 714 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 715 |
if(!S.playing)return;
|
| 716 |
-
var
|
| 717 |
-
if(
|
| 718 |
-
S.
|
| 719 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 720 |
}}
|
| 721 |
-
last=now-(dt%frameInterval);
|
| 722 |
-
S.time=S.time+dt/1000;
|
| 723 |
-
|
| 724 |
if(S.time>=S.dur){{
|
| 725 |
S.time=0;
|
| 726 |
if(S.dur===0){{S.playing=false;document.getElementById('playBtn').textContent='▶';return}}
|
| 727 |
}}
|
| 728 |
updateHead();
|
| 729 |
-
|
| 730 |
S.animId=requestAnimationFrame(loop);
|
| 731 |
}}
|
| 732 |
S.animId=requestAnimationFrame(loop);
|
| 733 |
}}
|
| 734 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 735 |
function stop(){{
|
| 736 |
if(S.animId){{cancelAnimationFrame(S.animId);S.animId=null}}
|
| 737 |
Object.keys(S.els).forEach(function(k){{
|
| 738 |
var el=S.els[k];
|
| 739 |
-
if(el&&el.pause)
|
|
|
|
|
|
|
| 740 |
}});
|
|
|
|
| 741 |
}}
|
| 742 |
|
| 743 |
function seek(t){{
|
| 744 |
S.time=Math.max(0,Math.min(S.dur||0,t));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
updateHead();
|
| 746 |
drawFrame();
|
| 747 |
}}
|
|
|
|
| 708 |
}}
|
| 709 |
|
| 710 |
function play(){{
|
| 711 |
+
// 현재 비주얼 클립 찾기
|
| 712 |
+
var vc=getClipAt(S.time,'visual');
|
| 713 |
+
if(vc&&vc.type==='video'){{
|
| 714 |
+
var el=S.els[vc.mid];
|
| 715 |
+
if(el){{
|
| 716 |
+
el.currentTime=S.time-vc.start+vc.ts;
|
| 717 |
+
el.play().catch(function(){{}});
|
| 718 |
+
}}
|
| 719 |
+
}}
|
| 720 |
+
// 오디오 클립들 시작
|
| 721 |
+
S.clips.forEach(function(c){{
|
| 722 |
+
if(c.type==='audio'){{
|
| 723 |
+
var cEnd=c.start+(c.te-c.ts);
|
| 724 |
+
if(S.time>=c.start&&S.time<cEnd){{
|
| 725 |
+
var el=S.els[c.mid];
|
| 726 |
+
if(el){{
|
| 727 |
+
el.currentTime=S.time-c.start+c.ts;
|
| 728 |
+
el.volume=S.muted?0:c.vol;
|
| 729 |
+
el.play().catch(function(){{}});
|
| 730 |
+
}}
|
| 731 |
+
}}
|
| 732 |
+
}}
|
| 733 |
+
}});
|
| 734 |
+
function loop(){{
|
| 735 |
if(!S.playing)return;
|
| 736 |
+
var vc=getClipAt(S.time,'visual');
|
| 737 |
+
if(vc&&vc.type==='video'){{
|
| 738 |
+
var el=S.els[vc.mid];
|
| 739 |
+
if(el&&!el.paused){{
|
| 740 |
+
S.time=vc.start+(el.currentTime-vc.ts);
|
| 741 |
+
}}else{{
|
| 742 |
+
S.time+=1/60;
|
| 743 |
+
}}
|
| 744 |
+
}}else{{
|
| 745 |
+
S.time+=1/60;
|
| 746 |
}}
|
|
|
|
|
|
|
|
|
|
| 747 |
if(S.time>=S.dur){{
|
| 748 |
S.time=0;
|
| 749 |
if(S.dur===0){{S.playing=false;document.getElementById('playBtn').textContent='▶';return}}
|
| 750 |
}}
|
| 751 |
updateHead();
|
| 752 |
+
drawFrameSmooth();
|
| 753 |
S.animId=requestAnimationFrame(loop);
|
| 754 |
}}
|
| 755 |
S.animId=requestAnimationFrame(loop);
|
| 756 |
}}
|
| 757 |
|
| 758 |
+
function drawFrameSmooth(){{
|
| 759 |
+
var t=S.time;
|
| 760 |
+
var vc=getClipAt(t,'visual');
|
| 761 |
+
var size=S.ratioSizes[S.ratio];
|
| 762 |
+
var pw=size.pw,ph=size.ph;
|
| 763 |
+
|
| 764 |
+
S.ctx.fillStyle='#000';
|
| 765 |
+
S.ctx.fillRect(0,0,pw,ph);
|
| 766 |
+
|
| 767 |
+
if(vc){{
|
| 768 |
+
var el=S.els[vc.mid];
|
| 769 |
+
if(el){{
|
| 770 |
+
if(vc.type==='video'){{
|
| 771 |
+
el.volume=S.muted?0:vc.vol;
|
| 772 |
+
}}
|
| 773 |
+
try{{
|
| 774 |
+
var sw=el.videoWidth||el.naturalWidth||el.width||pw;
|
| 775 |
+
var sh=el.videoHeight||el.naturalHeight||el.height||ph;
|
| 776 |
+
drawWithMode(S.ctx,el,sw,sh,pw,ph,S.fillMode);
|
| 777 |
+
}}catch(e){{}}
|
| 778 |
+
}}
|
| 779 |
+
}}else if(S.clips.length===0){{
|
| 780 |
+
S.ctx.fillStyle='#444';
|
| 781 |
+
S.ctx.font='14px sans-serif';
|
| 782 |
+
S.ctx.textAlign='center';
|
| 783 |
+
S.ctx.fillText('Add media to timeline',pw/2,ph/2);
|
| 784 |
+
}}
|
| 785 |
+
|
| 786 |
+
// 오디오 동기화 (가벼운 체크만)
|
| 787 |
+
S.clips.forEach(function(c){{
|
| 788 |
+
if(c.type!=='audio')return;
|
| 789 |
+
var cEnd=c.start+(c.te-c.ts);
|
| 790 |
+
var el=S.els[c.mid];
|
| 791 |
+
if(!el)return;
|
| 792 |
+
if(t>=c.start&&t<cEnd){{
|
| 793 |
+
el.volume=S.muted?0:c.vol;
|
| 794 |
+
if(el.paused)el.play().catch(function(){{}});
|
| 795 |
+
}}else{{
|
| 796 |
+
if(!el.paused)el.pause();
|
| 797 |
+
}}
|
| 798 |
+
}});
|
| 799 |
+
|
| 800 |
+
if(!vc&&S.clips.length>0){{
|
| 801 |
+
var hasText=getTextClipsAt(t).length>0;
|
| 802 |
+
if(!hasText){{
|
| 803 |
+
S.ctx.fillStyle='#333';
|
| 804 |
+
S.ctx.font='12px sans-serif';
|
| 805 |
+
S.ctx.textAlign='center';
|
| 806 |
+
S.ctx.fillText('No media at playhead',pw/2,ph/2);
|
| 807 |
+
}}
|
| 808 |
+
}}
|
| 809 |
+
|
| 810 |
+
var textClips=getTextClipsAt(t);
|
| 811 |
+
textClips.forEach(function(tc){{
|
| 812 |
+
drawText(S.ctx,tc,pw,ph);
|
| 813 |
+
}});
|
| 814 |
+
}}
|
| 815 |
+
|
| 816 |
function stop(){{
|
| 817 |
if(S.animId){{cancelAnimationFrame(S.animId);S.animId=null}}
|
| 818 |
Object.keys(S.els).forEach(function(k){{
|
| 819 |
var el=S.els[k];
|
| 820 |
+
if(el&&el.pause){{
|
| 821 |
+
el.pause();
|
| 822 |
+
}}
|
| 823 |
}});
|
| 824 |
+
drawFrame();
|
| 825 |
}}
|
| 826 |
|
| 827 |
function seek(t){{
|
| 828 |
S.time=Math.max(0,Math.min(S.dur||0,t));
|
| 829 |
+
// 비디오/오디오 위치도 동기화
|
| 830 |
+
S.clips.forEach(function(c){{
|
| 831 |
+
var el=S.els[c.mid];
|
| 832 |
+
if(!el)return;
|
| 833 |
+
var cEnd=c.start+(c.te-c.ts);
|
| 834 |
+
if(t>=c.start&&t<cEnd){{
|
| 835 |
+
el.currentTime=t-c.start+c.ts;
|
| 836 |
+
}}
|
| 837 |
+
}});
|
| 838 |
updateHead();
|
| 839 |
drawFrame();
|
| 840 |
}}
|