@work6189/poplayers 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # PoPlayers
2
+
3
+ jwplayer와 같은 현대적이고 커스터마이징 가능한 HTML5 비디오 PoPlayers입니다.
4
+
5
+ ## 기능
6
+
7
+ - 🎥 HTML5 비디오 지원
8
+ - 🎛️ 완전한 재생 컨트롤 (재생/일시정지, 진행바, 볼륨, 전체화면)
9
+ - ⚡ TypeScript로 작성
10
+ - 🎨 커스터마이징 가능한 테마
11
+ - 📱 반응형 디자인
12
+ - ⌨️ 키보드 단축키 지원
13
+ - 🔧 다양한 설정 옵션
14
+ - 🌐 CDN 및 NPM 배포 지원
15
+
16
+ ## 설치
17
+
18
+ ### NPM
19
+ ```bash
20
+ npm install @your-username/poplayers
21
+ ```
22
+
23
+ ### CDN
24
+ ```html
25
+ <link rel="stylesheet" href="https://unpkg.com/@your-username/poplayers@latest/dist/poplayers.css">
26
+ <script src="https://unpkg.com/@your-username/poplayers@latest/dist/poplayers.min.js"></script>
27
+ ```
28
+
29
+ ## 사용법
30
+
31
+ ### 기본 사용법
32
+
33
+ ```html
34
+ <div id="poplayers-container"></div>
35
+
36
+ <script>
37
+ const poplayers = new PoPlayers('poplayers-container', {
38
+ width: '800px',
39
+ height: '450px',
40
+ controls: true,
41
+ autoplay: false
42
+ });
43
+
44
+ poplayers.load('path/to/your/video.mp4');
45
+ </script>
46
+ ```
47
+
48
+ ### ES 모듈
49
+
50
+ ```javascript
51
+ import { createPoPlayers } from '@your-username/poplayers';
52
+
53
+ const poplayers = createPoPlayers('poplayers-container', {
54
+ width: '100%',
55
+ height: '400px',
56
+ theme: 'dark'
57
+ });
58
+
59
+ poplayers.load('https://example.com/video.mp4');
60
+ ```
61
+
62
+ ### TypeScript
63
+
64
+ ```typescript
65
+ import { createPoPlayers, PoPlayersConfig } from '@your-username/poplayers';
66
+
67
+ const config: PoPlayersConfig = {
68
+ width: '800px',
69
+ height: '450px',
70
+ controls: true,
71
+ autoplay: false,
72
+ theme: 'dark'
73
+ };
74
+
75
+ const poplayers = createPoPlayers('poplayers-container', config);
76
+ poplayers.load('video.mp4');
77
+ ```
78
+
79
+ ## 설정 옵션
80
+
81
+ ```typescript
82
+ interface PoPlayersConfig {
83
+ width?: string | number; // PoPlayers 너비
84
+ height?: string | number; // PoPlayers 높이
85
+ controls?: boolean; // 컨트롤 표시 여부
86
+ autoplay?: boolean; // 자동 재생
87
+ muted?: boolean; // 음소거
88
+ loop?: boolean; // 반복 재생
89
+ preload?: 'none' | 'metadata' | 'auto'; // 미리 로드
90
+ poster?: string; // 포스터 이미지
91
+ playbackRates?: number[]; // 재생 속도 옵션
92
+ volume?: number; // 초기 볼륨 (0-1)
93
+ startTime?: number; // 시작 시간 (초)
94
+ theme?: 'default' | 'dark' | 'light'; // 테마
95
+ responsive?: boolean; // 반응형
96
+ }
97
+ ```
98
+
99
+ ## API 메서드
100
+
101
+ ```javascript
102
+ // 재생 제어
103
+ poplayers.play(); // 재생
104
+ poplayers.pause(); // 일시정지
105
+ poplayers.stop(); // 중지
106
+
107
+ // 비디오 로드
108
+ poplayers.load('video.mp4'); // 단일 소스
109
+ poplayers.load([ // 다중 소스
110
+ { src: 'video.mp4', type: 'video/mp4' },
111
+ { src: 'video.webm', type: 'video/webm' }
112
+ ]);
113
+
114
+ // 탐색 및 설정
115
+ poplayers.seek(30); // 30초로 이동
116
+ poplayers.setVolume(0.5); // 볼륨 50%
117
+ poplayers.setPlaybackRate(1.5); // 1.5배속
118
+
119
+ // 전체화면
120
+ poplayers.enterFullscreen(); // 전체화면 진입
121
+ poplayers.exitFullscreen(); // 전체화면 해제
122
+
123
+ // 상태 조회
124
+ const state = poplayers.getState(); // 현재 상태 반환
125
+
126
+ // 정리
127
+ poplayers.destroy(); // PoPlayers 제거
128
+ ```
129
+
130
+ ## 이벤트
131
+
132
+ ```javascript
133
+ poplayers.on('ready', () => {
134
+ console.log('PoPlayers 준비됨');
135
+ });
136
+
137
+ poplayers.on('play', () => {
138
+ console.log('재생 시작');
139
+ });
140
+
141
+ poplayers.on('pause', () => {
142
+ console.log('일시정지');
143
+ });
144
+
145
+ poplayers.on('timeupdate', (currentTime) => {
146
+ console.log('현재 시간:', currentTime);
147
+ });
148
+
149
+ poplayers.on('error', (error) => {
150
+ console.error('에러 발생:', error);
151
+ });
152
+ ```
153
+
154
+ ## 키보드 단축키
155
+
156
+ - `Space`: 재생/일시정지
157
+ - `←`: 10초 뒤로
158
+ - `→`: 10초 앞으로
159
+ - `↑`: 볼륨 증가
160
+ - `↓`: 볼륨 감소
161
+ - `F`: 전체화면 토글
162
+ - `M`: 음소거 토글
163
+
164
+ ## 개발
165
+
166
+ ```bash
167
+ # 의존성 설치
168
+ npm install
169
+
170
+ # 개발 서버 실행
171
+ npm run dev
172
+
173
+ # 빌드
174
+ npm run build
175
+
176
+ # 타입 체크
177
+ npm run type-check
178
+
179
+ # 린트
180
+ npm run lint
181
+
182
+ # 테스트
183
+ npm test
184
+ ```
185
+
186
+ ## 브라우저 지원
187
+
188
+ - Chrome 60+
189
+ - Firefox 55+
190
+ - Safari 12+
191
+ - Edge 79+
192
+
193
+ ## 라이선스
194
+
195
+ MIT License
196
+
197
+ ## 기여
198
+
199
+ 1. Fork the Project
200
+ 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
201
+ 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
202
+ 4. Push to the Branch (`git push origin feature/AmazingFeature`)
203
+ 5. Open a Pull Request
204
+
205
+ ## 변경 사항
206
+
207
+ ### v1.0.0
208
+ - 초기 릴리스
209
+ - 기본 비디오 PoPlayers 기능
210
+ - TypeScript 지원
211
+ - 다양한 테마 지원
212
+ - 반응형 디자인
@@ -0,0 +1,74 @@
1
+ <!doctype html><html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>PoPlayers Demo</title><script defer="defer" src="poplayers.min.js"></script><link href="poplayers.css" rel="stylesheet"></head><body><div id="app"><h1>PoPlayers Demo</h1><div id="poplayers-container" style="width: 800px; height: 450px; margin: 20px auto;"></div><div style="text-align: center; margin: 20px;"><h3>테스트 비디오 URL:</h3><input id="video-url" placeholder="비디오 URL을 입력하세요" style="width: 400px; padding: 8px;"> <button onclick="loadVideo()">비디오 로드</button></div><div style="text-align: center;"><button onclick="testHLS()">HLS 테스트</button> <button onclick="testMP4()">MP4 테스트</button></div></div><script>let poplayers;
2
+
3
+ document.addEventListener('DOMContentLoaded', function() {
4
+ // 플레이어 초기화 - 팩토리 함수 사용
5
+ try {
6
+ if (typeof PoPlayers !== 'undefined') {
7
+ // 생성자 함수로 호출 (new 키워드 사용)
8
+ poplayers = new PoPlayers('poplayers-container', {
9
+ width: '100%',
10
+ height: '100%',
11
+ controls: true,
12
+ autoplay: false,
13
+ muted: false
14
+ });
15
+ } else if (typeof window.PoPlayers !== 'undefined') {
16
+ // 윈도우 객체에서 생성자 함수로 호출
17
+ poplayers = new window.PoPlayers('poplayers-container', {
18
+ width: '100%',
19
+ height: '100%',
20
+ controls: true,
21
+ autoplay: false,
22
+ muted: false
23
+ });
24
+ } else {
25
+ console.error('PoPlayers is not available');
26
+ alert('PoPlayers를 로드할 수 없습니다. 페이지를 새로고침해주세요.');
27
+ return;
28
+ }
29
+ } catch (error) {
30
+ console.error('PoPlayers 초기화 에러:', error);
31
+ alert('PoPlayers 초기화에 실패했습니다: ' + error.message);
32
+ return;
33
+ }
34
+
35
+ // PoPlayers가 준비되었을 때 이벤트 리스너 추가
36
+ poplayers.on('ready', function() {
37
+ console.log('PoPlayers가 준비되었습니다!');
38
+ });
39
+
40
+ poplayers.on('error', function(error) {
41
+ console.error('PoPlayers 에러:', error);
42
+ });
43
+ });
44
+
45
+ function loadVideo() {
46
+ if (!poplayers) {
47
+ alert('PoPlayers가 초기화되지 않았습니다.');
48
+ return;
49
+ }
50
+ const url = document.getElementById('video-url').value;
51
+ if (url) {
52
+ poplayers.load(url);
53
+ } else {
54
+ alert('비디오 URL을 입력해주세요.');
55
+ }
56
+ }
57
+
58
+ function testHLS() {
59
+ if (!poplayers) {
60
+ alert('PoPlayers가 초기화되지 않았습니다.');
61
+ return;
62
+ }
63
+ // HLS 테스트 URL (실제 사용시 유효한 URL로 변경)
64
+ poplayers.load('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8');
65
+ }
66
+
67
+ function testMP4() {
68
+ if (!poplayers) {
69
+ alert('PoPlayers가 초기화되지 않았습니다.');
70
+ return;
71
+ }
72
+ // MP4 테스트 URL (실제 사용시 유효한 URL로 변경)
73
+ poplayers.load('https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4');
74
+ }</script></body></html>
@@ -0,0 +1 @@
1
+ :root{--player-primary-color: #007bff;--player-secondary-color: #6c757d;--player-background: #000;--player-controls-bg: rgba(0, 0, 0, 0.7);--player-text-color: #fff;--player-border-radius: 4px;--player-transition: all 0.3s ease;--player-shadow: 0 2px 10px rgba(0, 0, 0, 0.3)}.poplayers{position:relative;background:var(--player-background);border-radius:var(--player-border-radius);overflow:hidden;box-shadow:var(--player-shadow);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}.poplayers:focus{outline:2px solid var(--player-primary-color);outline-offset:2px}.poplayers .video-element{width:100%;height:100%;display:block;background:var(--player-background)}.poplayers .player-controls{position:absolute;bottom:0;left:0;right:0;background:linear-gradient(transparent, var(--player-controls-bg));color:var(--player-text-color);opacity:0;transform:translateY(100%);transition:var(--player-transition);pointer-events:none;padding:20px 0 0}.poplayers .player-controls.visible{opacity:1;transform:translateY(0);pointer-events:all}.poplayers:hover .player-controls{opacity:1;transform:translateY(0);pointer-events:all}.poplayers .controls-bar{display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--player-controls-bg);backdrop-filter:blur(10px)}.poplayers .control-button{background:none;border:none;color:var(--player-text-color);cursor:pointer;padding:8px;border-radius:var(--player-border-radius);transition:var(--player-transition);display:flex;align-items:center;justify-content:center;min-width:40px;height:40px}.poplayers .control-button:hover{background:hsla(0,0%,100%,.1);transform:scale(1.05)}.poplayers .control-button:active{transform:scale(0.95)}.poplayers .control-button:focus{outline:2px solid var(--player-primary-color);outline-offset:2px}.poplayers .control-button svg{width:24px;height:24px;fill:currentColor}.poplayers .play-button{background:var(--player-primary-color)}.poplayers .play-button:hover{background:rgba(0,123,255,.8)}.poplayers .progress-container{flex:1;margin:0 12px}.poplayers .progress-bar{position:relative;height:6px;background:hsla(0,0%,100%,.3);border-radius:3px;cursor:pointer;transition:height .2s ease}.poplayers .progress-bar:hover{height:8px}.poplayers .progress-bar:hover .progress-handle{opacity:1;transform:scale(1)}.poplayers .progress-filled{height:100%;background:var(--player-primary-color);border-radius:3px;transition:width .1s ease;position:relative}.poplayers .progress-filled::after{content:"";position:absolute;right:0;top:50%;transform:translateY(-50%);width:2px;height:120%;background:hsla(0,0%,100%,.8);border-radius:1px}.poplayers .progress-handle{position:absolute;top:50%;transform:translateY(-50%) scale(0);width:16px;height:16px;background:var(--player-primary-color);border:2px solid #fff;border-radius:50%;cursor:pointer;opacity:0;transition:all .2s ease;box-shadow:0 2px 6px rgba(0,0,0,.3)}.poplayers .progress-handle:hover{transform:translateY(-50%) scale(1.2)}.poplayers .time-display{font-size:14px;font-weight:500;color:var(--player-text-color);min-width:120px;text-align:center;font-variant-numeric:tabular-nums}.poplayers .volume-container{display:flex;align-items:center;gap:8px}.poplayers .volume-container:hover .volume-slider{opacity:1;width:80px}.poplayers .volume-slider{-webkit-appearance:none;appearance:none;width:0;height:4px;background:hsla(0,0%,100%,.3);border-radius:2px;outline:none;opacity:0;transition:all .3s ease;cursor:pointer}.poplayers .volume-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;background:var(--player-primary-color);border-radius:50%;cursor:pointer;border:2px solid #fff;box-shadow:0 2px 6px rgba(0,0,0,.3);transition:transform .2s ease}.poplayers .volume-slider::-webkit-slider-thumb:hover{transform:scale(1.2)}.poplayers .volume-slider::-moz-range-thumb{width:16px;height:16px;background:var(--player-primary-color);border-radius:50%;cursor:pointer;border:2px solid #fff;box-shadow:0 2px 6px rgba(0,0,0,.3)}.poplayers .rate-button{font-size:14px;font-weight:600;min-width:48px}.poplayers .fullscreen-button:hover{color:var(--player-primary-color)}.poplayers:-webkit-full-screen{width:100vw !important;height:100vh !important}.poplayers:-moz-full-screen{width:100vw !important;height:100vh !important}.poplayers:fullscreen{width:100vw !important;height:100vh !important}.poplayers[data-theme=dark]{--player-background: #1a1a1a;--player-controls-bg: rgba(0, 0, 0, 0.8);--player-text-color: #ffffff}.poplayers[data-theme=light]{--player-background: #f8f9fa;--player-controls-bg: rgba(255, 255, 255, 0.9);--player-text-color: #333333;--player-shadow: 0 2px 10px rgba(0, 0, 0, 0.1)}@media(max-width: 768px){.poplayers .controls-bar{gap:8px;padding:8px 12px}.poplayers .control-button{min-width:36px;height:36px;padding:6px}.poplayers .control-button svg{width:20px;height:20px}.poplayers .time-display{font-size:12px;min-width:100px}.poplayers .progress-container{margin:0 8px}.poplayers .volume-container:hover .volume-slider{width:60px}}@media(max-width: 480px){.poplayers .controls-bar{gap:4px;padding:6px 8px}.poplayers .control-button{min-width:32px;height:32px;padding:4px}.poplayers .control-button svg{width:18px;height:18px}.poplayers .time-display{font-size:11px;min-width:80px}.poplayers .rate-button{font-size:12px;min-width:40px}.poplayers .volume-slider{display:none}}.custom-video-player .loading-spinner{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:40px;height:40px;border:3px solid hsla(0,0%,100%,.3);border-top:3px solid var(--player-primary-color);border-radius:50%;animation:spin 1s linear infinite;z-index:10}@keyframes spin{0%{transform:translate(-50%, -50%) rotate(0deg)}100%{transform:translate(-50%, -50%) rotate(360deg)}}.custom-video-player .error-message{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(220,53,69,.9);color:#fff;padding:16px 24px;border-radius:var(--player-border-radius);font-size:14px;text-align:center;z-index:10;max-width:80%}.poplayers:focus-within{outline:2px solid var(--player-primary-color);outline-offset:2px}@media(prefers-contrast: high){.poplayers{--player-primary-color: #0056b3;--player-controls-bg: rgba(0, 0, 0, 0.9);border:2px solid var(--player-text-color)}}@media(prefers-reduced-motion: reduce){.custom-video-player *{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important}}
@@ -0,0 +1 @@
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.PoPlayers=t():e.PoPlayers=t()}(this,()=>(()=>{"use strict";var e={d:(t,i)=>{for(var s in i)e.o(i,s)&&!e.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:i[s]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)},t={};e.d(t,{default:()=>d});class i{constructor(){this.listeners=new Map}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){const i=this.listeners.get(e);i&&(i.delete(t),0===i.size&&this.listeners.delete(e))}emit(e,...t){const i=this.listeners.get(e);i&&i.forEach(i=>{try{i(...t)}catch(t){console.error(`Error in event listener for ${String(e)}:`,t)}})}removeAllListeners(e){e?this.listeners.delete(e):this.listeners.clear()}listenerCount(e){const t=this.listeners.get(e);return t?t.size:0}}class s extends i{}function n(e,t,i){const s=document.createElement(e);return t&&(s.className=t),i&&Object.entries(i).forEach(([e,t])=>{s.setAttribute(e,t)}),s}function o(e){if(isNaN(e)||!isFinite(e))return"0:00";const t=Math.floor(e/3600),i=Math.floor(e%3600/60),s=Math.floor(e%60);return t>0?`${t}:${i.toString().padStart(2,"0")}:${s.toString().padStart(2,"0")}`:`${i}:${s.toString().padStart(2,"0")}`}function l(e,t){let i=null,s=0;return(...n)=>{const o=Date.now();o-s>t?(e(...n),s=o):(i&&clearTimeout(i),i=setTimeout(()=>{e(...n),s=Date.now()},t-(o-s)))}}class r{constructor(e,t){this.isVisible=!0,this.hideTimeout=null,this.player=e,this.config=t,this.createControls(),this.setupEventListeners()}createControls(){this.controlsElement=n("div","player-controls");const e=n("div","controls-bar");this.playButton=n("button","control-button play-button"),this.playButton.innerHTML=this.getPlayIcon(),this.playButton.title="재생";const t=n("div","progress-container");this.progressBar=n("div","progress-bar"),this.progressFilled=n("div","progress-filled"),this.progressHandle=n("div","progress-handle"),this.progressBar.appendChild(this.progressFilled),this.progressBar.appendChild(this.progressHandle),t.appendChild(this.progressBar),this.timeDisplay=n("div","time-display"),this.timeDisplay.textContent="0:00 / 0:00";const i=n("div","volume-container");this.volumeButton=n("button","control-button volume-button"),this.volumeButton.innerHTML=this.getVolumeIcon(this.config.volume),this.volumeButton.title="음소거",this.volumeSlider=n("input","volume-slider"),this.volumeSlider.type="range",this.volumeSlider.min="0",this.volumeSlider.max="1",this.volumeSlider.step="0.1",this.volumeSlider.value=this.config.volume.toString(),i.appendChild(this.volumeButton),i.appendChild(this.volumeSlider),this.playbackRateButton=n("button","control-button rate-button"),this.playbackRateButton.textContent="1x",this.playbackRateButton.title="재생 속도",this.fullscreenButton=n("button","control-button fullscreen-button"),this.fullscreenButton.innerHTML=this.getFullscreenIcon(),this.fullscreenButton.title="전체화면",e.appendChild(this.playButton),e.appendChild(t),e.appendChild(this.timeDisplay),e.appendChild(i),e.appendChild(this.playbackRateButton),e.appendChild(this.fullscreenButton),this.controlsElement.appendChild(e)}setupEventListeners(){this.playButton.addEventListener("click",()=>{this.player.getState().isPlaying?this.player.pause():this.player.play()}),this.progressBar.addEventListener("click",e=>{const t=this.progressBar.getBoundingClientRect(),i=(e.clientX-t.left)/t.width*this.player.getState().duration;this.player.seek(i)});let e=!1;this.progressHandle.addEventListener("mousedown",()=>{e=!0}),document.addEventListener("mousemove",t=>{if(!e)return;const i=this.progressBar.getBoundingClientRect(),s=Math.max(0,Math.min(1,(t.clientX-i.left)/i.width))*this.player.getState().duration;this.player.seek(s)}),document.addEventListener("mouseup",()=>{e=!1}),this.volumeButton.addEventListener("click",()=>{const e=this.player.getState();e.isMuted||0===e.volume?this.player.setVolume(this.config.volume):this.player.setVolume(0)}),this.volumeSlider.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.player.setVolume(t)});let t=this.config.playbackRates.indexOf(1);this.playbackRateButton.addEventListener("click",()=>{t=(t+1)%this.config.playbackRates.length;const e=this.config.playbackRates[t];this.player.setPlaybackRate(e),this.playbackRateButton.textContent=`${e}x`}),this.fullscreenButton.addEventListener("click",()=>{this.player.getState().isFullscreen?this.player.exitFullscreen():this.player.enterFullscreen()}),this.player.on("play",()=>{this.playButton.innerHTML=this.getPauseIcon(),this.playButton.title="일시정지"}),this.player.on("pause",()=>{this.playButton.innerHTML=this.getPlayIcon(),this.playButton.title="재생"}),this.player.on("timeupdate",e=>{this.updateProgress(e)}),this.player.on("durationchange",e=>{this.updateTimeDisplay(this.player.getState().currentTime,e)}),this.player.on("volumechange",e=>{this.volumeSlider.value=e.toString(),this.volumeButton.innerHTML=this.getVolumeIcon(e)}),this.player.on("fullscreenchange",e=>{this.fullscreenButton.innerHTML=e?this.getExitFullscreenIcon():this.getFullscreenIcon(),this.fullscreenButton.title=e?"전체화면 해제":"전체화면"});const i=this.player.getContainer(),s=l(()=>{this.showControls()},100);i.addEventListener("mousemove",s),i.addEventListener("mouseleave",()=>{this.hideControls()}),document.addEventListener("keydown",e=>{if(this.isPlayerFocused())switch(e.code){case"Space":e.preventDefault(),this.playButton.click();break;case"ArrowLeft":e.preventDefault(),this.player.seek(this.player.getState().currentTime-10);break;case"ArrowRight":e.preventDefault(),this.player.seek(this.player.getState().currentTime+10);break;case"ArrowUp":e.preventDefault(),this.player.setVolume(Math.min(1,this.player.getState().volume+.1));break;case"ArrowDown":e.preventDefault(),this.player.setVolume(Math.max(0,this.player.getState().volume-.1));break;case"KeyF":e.preventDefault(),this.fullscreenButton.click();break;case"KeyM":e.preventDefault(),this.volumeButton.click()}})}updateProgress(e){const t=this.player.getState();if(t.duration>0){const i=e/t.duration*100;this.progressFilled.style.width=`${i}%`,this.progressHandle.style.left=`${i}%`}this.updateTimeDisplay(e,t.duration)}updateTimeDisplay(e,t){const i=o(e),s=o(t);this.timeDisplay.textContent=`${i} / ${s}`}showControls(){this.isVisible=!0,this.controlsElement.classList.add("visible"),this.hideTimeout&&clearTimeout(this.hideTimeout),this.hideTimeout=setTimeout(()=>{this.hideControls()},3e3)}hideControls(){this.hideTimeout&&clearTimeout(this.hideTimeout),this.isVisible=!1,this.controlsElement.classList.remove("visible")}isPlayerFocused(){const e=this.player.getContainer();return e.contains(document.activeElement)||document.activeElement===e}getPlayIcon(){return'<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M8 5v14l11-7z"/>\n </svg>'}getPauseIcon(){return'<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>\n </svg>'}getVolumeIcon(e){return 0===e?'<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>\n </svg>':e<.5?'<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/>\n </svg>':'<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>\n </svg>'}getFullscreenIcon(){return'<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>\n </svg>'}getExitFullscreenIcon(){return'<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"/>\n </svg>'}getElement(){return this.controlsElement}destroy(){this.hideTimeout&&clearTimeout(this.hideTimeout),this.controlsElement.parentNode&&this.controlsElement.parentNode.removeChild(this.controlsElement)}}class a extends s{constructor(e,t={}){if(super(),this.isDestroyed=!1,this.config={width:"100%",height:"100%",controls:!0,autoplay:!1,muted:!1,loop:!1,preload:"metadata",poster:"",playbackRates:[.5,.75,1,1.25,1.5,2],volume:1,startTime:0,theme:"default",responsive:!0,...t},this.container="string"==typeof e?document.getElementById(e):e,!this.container)throw new Error(`Container with id "${e}" not found`);this.init()}init(){this.setupContainer(),this.createVideoElement(),this.setupEventListeners(),this.config.controls&&(this.controls=new r(this,this.config),this.container.appendChild(this.controls.getElement())),this.emit("ready")}setupContainer(){this.container.classList.add("poplayers"),this.container.style.position="relative",this.container.style.width="number"==typeof this.config.width?`${this.config.width}px`:this.config.width,this.container.style.height="number"==typeof this.config.height?`${this.config.height}px`:this.config.height}createVideoElement(){this.videoElement=n("video","video-element"),this.videoElement.preload=this.config.preload,this.videoElement.muted=this.config.muted,this.videoElement.loop=this.config.loop,this.videoElement.volume=this.config.volume,this.config.poster&&(this.videoElement.poster=this.config.poster),this.videoElement.style.width="100%",this.videoElement.style.height="100%",this.videoElement.style.display="block",this.container.appendChild(this.videoElement)}setupEventListeners(){const e=l(e=>{const t=e.target;this.emit("timeupdate",t.currentTime)},250);this.videoElement.addEventListener("loadstart",()=>this.emit("loadstart")),this.videoElement.addEventListener("loadeddata",()=>this.emit("loadeddata")),this.videoElement.addEventListener("canplay",()=>this.emit("canplay")),this.videoElement.addEventListener("play",()=>this.emit("play")),this.videoElement.addEventListener("pause",()=>this.emit("pause")),this.videoElement.addEventListener("ended",()=>this.emit("ended")),this.videoElement.addEventListener("timeupdate",e),this.videoElement.addEventListener("durationchange",e=>{const t=e.target;this.emit("durationchange",t.duration)}),this.videoElement.addEventListener("volumechange",e=>{const t=e.target;this.emit("volumechange",t.volume)}),this.videoElement.addEventListener("ratechange",e=>{const t=e.target;this.emit("ratechange",t.playbackRate)}),this.videoElement.addEventListener("seeking",()=>this.emit("seeking")),this.videoElement.addEventListener("seeked",()=>this.emit("seeked")),this.videoElement.addEventListener("error",e=>{const t=e.target.error;t&&this.emit("error",new Error(`Video error: ${t.message}`))}),document.addEventListener("fullscreenchange",()=>{this.emit("fullscreenchange",!!document.fullscreenElement)}),document.addEventListener("webkitfullscreenchange",()=>{this.emit("fullscreenchange",!!document.webkitFullscreenElement)}),document.addEventListener("mozfullscreenchange",()=>{this.emit("fullscreenchange",!!document.mozFullScreenElement)}),document.addEventListener("MSFullscreenChange",()=>{this.emit("fullscreenchange",!!document.msFullscreenElement)})}async play(){if(!this.isDestroyed)try{await this.videoElement.play()}catch(e){throw this.emit("error",e),e}}pause(){this.isDestroyed||this.videoElement.pause()}stop(){this.isDestroyed||(this.pause(),this.seek(0))}load(e){if(!this.isDestroyed){for(;this.videoElement.firstChild;)this.videoElement.removeChild(this.videoElement.firstChild);"string"==typeof e?this.videoElement.src=e:e.forEach(e=>{const t=n("source");t.src=e.src,e.type&&(t.type=e.type),this.videoElement.appendChild(t)}),this.videoElement.load(),this.config.startTime>0&&this.videoElement.addEventListener("loadeddata",()=>{this.seek(this.config.startTime)},{once:!0}),this.config.autoplay&&this.videoElement.addEventListener("canplay",()=>{this.play().catch(e=>{console.warn("Autoplay failed:",e)})},{once:!0})}}seek(e){this.isDestroyed||(this.videoElement.currentTime=Math.max(0,Math.min(e,this.videoElement.duration||0)))}setVolume(e){if(this.isDestroyed)return;const t=Math.max(0,Math.min(1,e));this.videoElement.volume=t,this.videoElement.muted=0===t}setPlaybackRate(e){this.isDestroyed||(this.videoElement.playbackRate=e)}enterFullscreen(){var e;!this.isDestroyed&&(document.fullscreenEnabled||document.webkitFullscreenEnabled||document.mozFullScreenEnabled||document.msFullscreenEnabled)&&((e=this.container).requestFullscreen?e.requestFullscreen():e.webkitRequestFullscreen?e.webkitRequestFullscreen():e.mozRequestFullScreen?e.mozRequestFullScreen():e.msRequestFullscreen&&e.msRequestFullscreen())}exitFullscreen(){this.isDestroyed||(document.exitFullscreen?document.exitFullscreen():document.webkitExitFullscreen?document.webkitExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen())}getState(){return this.isDestroyed?{isPlaying:!1,isPaused:!0,isEnded:!1,isMuted:!1,isFullscreen:!1,currentTime:0,duration:0,volume:0,playbackRate:1,buffered:null,seekable:null}:{isPlaying:!this.videoElement.paused&&!this.videoElement.ended,isPaused:this.videoElement.paused,isEnded:this.videoElement.ended,isMuted:this.videoElement.muted,isFullscreen:!!document.fullscreenElement,currentTime:this.videoElement.currentTime,duration:this.videoElement.duration||0,volume:this.videoElement.volume,playbackRate:this.videoElement.playbackRate,buffered:this.videoElement.buffered,seekable:this.videoElement.seekable}}destroy(){this.isDestroyed||(this.pause(),this.removeAllListeners(),this.controls&&this.controls.destroy(),this.container&&this.videoElement&&this.container.removeChild(this.videoElement),this.isDestroyed=!0)}getVideoElement(){return this.videoElement}getContainer(){return this.container}getConfig(){return{...this.config}}}function h(e,t){return new a(e,t)}h.createPoPlayers=function(e,t){return new a(e,t)},h.VideoPlayer=a,h.version="1.0.0","undefined"!=typeof window&&(window.PoPlayers=h,globalThis.PoPlayers=h);const d=h;return t.default})());
@@ -0,0 +1,4 @@
1
+ declare const originalError: {
2
+ (...data: any[]): void;
3
+ (message?: any, ...optionalParams: any[]): void;
4
+ };
@@ -0,0 +1,33 @@
1
+ import { PoPlayersConfig } from '../types';
2
+ import { VideoPlayer } from './VideoPlayer';
3
+ export declare class Controls {
4
+ private player;
5
+ private config;
6
+ private controlsElement;
7
+ private playButton;
8
+ private progressBar;
9
+ private progressFilled;
10
+ private progressHandle;
11
+ private timeDisplay;
12
+ private volumeButton;
13
+ private volumeSlider;
14
+ private fullscreenButton;
15
+ private playbackRateButton;
16
+ private isVisible;
17
+ private hideTimeout;
18
+ constructor(player: VideoPlayer, config: Required<PoPlayersConfig>);
19
+ private createControls;
20
+ private setupEventListeners;
21
+ private updateProgress;
22
+ private updateTimeDisplay;
23
+ private showControls;
24
+ private hideControls;
25
+ private isPlayerFocused;
26
+ private getPlayIcon;
27
+ private getPauseIcon;
28
+ private getVolumeIcon;
29
+ private getFullscreenIcon;
30
+ private getExitFullscreenIcon;
31
+ getElement(): HTMLElement;
32
+ destroy(): void;
33
+ }
@@ -0,0 +1,28 @@
1
+ import { PoPlayersConfig, PoPlayersState, VideoSource, PoPlayersInstance } from '../types';
2
+ import { PoPlayersEventEmitter } from '../utils/events';
3
+ export declare class VideoPlayer extends PoPlayersEventEmitter implements PoPlayersInstance {
4
+ private container;
5
+ private videoElement;
6
+ private controls;
7
+ private config;
8
+ private isDestroyed;
9
+ constructor(containerId: string | HTMLElement, config?: PoPlayersConfig);
10
+ private init;
11
+ private setupContainer;
12
+ private createVideoElement;
13
+ private setupEventListeners;
14
+ play(): Promise<void>;
15
+ pause(): void;
16
+ stop(): void;
17
+ load(source: string | VideoSource[]): void;
18
+ seek(time: number): void;
19
+ setVolume(volume: number): void;
20
+ setPlaybackRate(rate: number): void;
21
+ enterFullscreen(): void;
22
+ exitFullscreen(): void;
23
+ getState(): PoPlayersState;
24
+ destroy(): void;
25
+ getVideoElement(): HTMLVideoElement;
26
+ getContainer(): HTMLElement;
27
+ getConfig(): Required<PoPlayersConfig>;
28
+ }
@@ -0,0 +1,14 @@
1
+ import { VideoPlayer } from './components/VideoPlayer';
2
+ import { PoPlayersConfig, PoPlayersInstance } from './types';
3
+ import './styles/poplayers.scss';
4
+ declare function createPoPlayers(containerId: string | HTMLElement, config?: PoPlayersConfig): PoPlayersInstance;
5
+ declare function PoPlayersConstructor(containerId: string | HTMLElement, config?: PoPlayersConfig): PoPlayersInstance;
6
+ declare namespace PoPlayersConstructor {
7
+ var createPoPlayers: typeof import(".").createPoPlayers;
8
+ var VideoPlayer: typeof import("./components/VideoPlayer").VideoPlayer;
9
+ var version: string;
10
+ }
11
+ export { VideoPlayer, createPoPlayers };
12
+ export type { PoPlayersConfig, PoPlayersInstance };
13
+ export default PoPlayersConstructor;
14
+ export type * from './types';
@@ -0,0 +1,77 @@
1
+ export interface PoPlayersConfig {
2
+ width?: string | number;
3
+ height?: string | number;
4
+ controls?: boolean;
5
+ autoplay?: boolean;
6
+ muted?: boolean;
7
+ loop?: boolean;
8
+ preload?: 'none' | 'metadata' | 'auto';
9
+ poster?: string;
10
+ playbackRates?: number[];
11
+ volume?: number;
12
+ startTime?: number;
13
+ theme?: 'default' | 'dark' | 'light';
14
+ responsive?: boolean;
15
+ }
16
+ export interface PoPlayersEvents {
17
+ 'ready': () => void;
18
+ 'play': () => void;
19
+ 'pause': () => void;
20
+ 'ended': () => void;
21
+ 'timeupdate': (currentTime: number) => void;
22
+ 'durationchange': (duration: number) => void;
23
+ 'volumechange': (volume: number) => void;
24
+ 'ratechange': (rate: number) => void;
25
+ 'fullscreenchange': (isFullscreen: boolean) => void;
26
+ 'error': (error: Error) => void;
27
+ 'loadstart': () => void;
28
+ 'loadeddata': () => void;
29
+ 'canplay': () => void;
30
+ 'seeking': () => void;
31
+ 'seeked': () => void;
32
+ }
33
+ export interface VideoSource {
34
+ src: string;
35
+ type?: string;
36
+ label?: string;
37
+ default?: boolean;
38
+ }
39
+ export interface PoPlayersState {
40
+ isPlaying: boolean;
41
+ isPaused: boolean;
42
+ isEnded: boolean;
43
+ isMuted: boolean;
44
+ isFullscreen: boolean;
45
+ currentTime: number;
46
+ duration: number;
47
+ volume: number;
48
+ playbackRate: number;
49
+ buffered: TimeRanges | null;
50
+ seekable: TimeRanges | null;
51
+ }
52
+ export interface ControlsConfig {
53
+ play?: boolean;
54
+ pause?: boolean;
55
+ progress?: boolean;
56
+ time?: boolean;
57
+ volume?: boolean;
58
+ fullscreen?: boolean;
59
+ playbackRate?: boolean;
60
+ quality?: boolean;
61
+ }
62
+ export type EventListener<T extends keyof PoPlayersEvents> = PoPlayersEvents[T];
63
+ export interface PoPlayersInstance {
64
+ play(): Promise<void>;
65
+ pause(): void;
66
+ stop(): void;
67
+ load(source: string | VideoSource[]): void;
68
+ seek(time: number): void;
69
+ setVolume(volume: number): void;
70
+ setPlaybackRate(rate: number): void;
71
+ enterFullscreen(): void;
72
+ exitFullscreen(): void;
73
+ destroy(): void;
74
+ on<T extends keyof PoPlayersEvents>(event: T, listener: EventListener<T>): void;
75
+ off<T extends keyof PoPlayersEvents>(event: T, listener: EventListener<T>): void;
76
+ getState(): PoPlayersState;
77
+ }
@@ -0,0 +1,7 @@
1
+ export declare function createElement<K extends keyof HTMLElementTagNameMap>(tagName: K, className?: string, attributes?: Record<string, string>): HTMLElementTagNameMap[K];
2
+ export declare function formatTime(seconds: number): string;
3
+ export declare function throttle<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => void;
4
+ export declare function debounce<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => void;
5
+ export declare function isFullscreenSupported(): boolean;
6
+ export declare function requestFullscreen(element: HTMLElement): void;
7
+ export declare function exitFullscreen(): void;
@@ -0,0 +1,11 @@
1
+ import { PoPlayersEvents } from '../types';
2
+ export declare class EventEmitter<T extends Record<string, (...args: any[]) => void>> {
3
+ private listeners;
4
+ on<K extends keyof T>(event: K, listener: T[K]): void;
5
+ off<K extends keyof T>(event: K, listener: T[K]): void;
6
+ emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void;
7
+ removeAllListeners(event?: keyof T): void;
8
+ listenerCount(event: keyof T): number;
9
+ }
10
+ export declare class PoPlayersEventEmitter extends EventEmitter<PoPlayersEvents & Record<string, (...args: any[]) => void>> {
11
+ }
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@work6189/poplayers",
3
+ "version": "1.0.3",
4
+ "description": "A modern, customizable video PoPlayers similar to JWPlayer",
5
+ "main": "dist/poplayers.js",
6
+ "module": "dist/poplayers.esm.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "files": [
9
+ "dist/"
10
+ ],
11
+ "scripts": {
12
+ "dev": "webpack serve --mode development",
13
+ "build": "webpack --mode production",
14
+ "build:dev": "webpack --mode development",
15
+ "build:vercel": "npm run build",
16
+ "start": "npm run build:vercel && npx serve dist -p ${PORT:-3000}",
17
+ "preview": "npm run build:vercel && npx serve dist -p 3000",
18
+ "test": "jest",
19
+ "lint": "eslint src/**/*.ts",
20
+ "type-check": "tsc --noEmit",
21
+ "prepare": "npm run build",
22
+ "prepublishOnly": "npm run test && npm run lint && npm run type-check && npm run build",
23
+ "publish:npm": "./scripts/publish.sh",
24
+ "release:patch": "npm version patch && git push --follow-tags",
25
+ "release:minor": "npm version minor && git push --follow-tags",
26
+ "release:major": "npm version major && git push --follow-tags",
27
+ "release:prerelease": "npm version prerelease && git push --follow-tags",
28
+ "version": "npm run build",
29
+ "postversion": "echo 'Version updated. Push tags to trigger release: git push --follow-tags'"
30
+ },
31
+ "keywords": [
32
+ "video",
33
+ "poplayers",
34
+ "html5",
35
+ "streaming",
36
+ "media"
37
+ ],
38
+ "author": "Your Name",
39
+ "license": "MIT",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/work6189/poplayers.git"
43
+ },
44
+ "homepage": "https://github.com/work6189/poplayers#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/work6189/poplayers/issues"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "devDependencies": {
52
+ "@types/jest": "^29.5.0",
53
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
54
+ "@typescript-eslint/parser": "^7.18.0",
55
+ "css-loader": "^6.8.0",
56
+ "eslint": "^8.57.1",
57
+ "html-webpack-plugin": "^5.5.0",
58
+ "identity-obj-proxy": "^3.0.0",
59
+ "jest": "^29.5.0",
60
+ "jest-environment-jsdom": "^29.5.0",
61
+ "mini-css-extract-plugin": "^2.7.0",
62
+ "sass": "^1.77.0",
63
+ "sass-embedded": "^1.89.2",
64
+ "sass-loader": "^14.0.0",
65
+ "style-loader": "^3.3.0",
66
+ "ts-jest": "^29.1.0",
67
+ "ts-loader": "^9.4.0",
68
+ "typescript": "^5.0.0",
69
+ "webpack": "^5.80.0",
70
+ "webpack-cli": "^5.1.0",
71
+ "webpack-dev-server": "^4.15.0"
72
+ },
73
+ "dependencies": {
74
+ "serve": "^14.2.0"
75
+ }
76
+ }