mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat: initialize Video.js 10 monorepo with core architecture
- Set up monorepo structure with core, HTML, React, and React Native packages - Add TypeScript configuration with project references - Create foundational packages: media-store, playback-engine, media, icons - Implement platform-specific packages for HTML, React, and React Native - Add comprehensive documentation and development tooling - Include CLAUDE.md with development guidelines and conventional commits 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@vjs-10/html",
|
||||
"version": "0.1.0",
|
||||
"description": "Core UI components for building media player UIs. Also an import + export of various other HTML packages for convenience and discoverability.",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "echo \"No tests yet\"",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"keywords": [
|
||||
"media",
|
||||
"player",
|
||||
"html",
|
||||
"ui",
|
||||
"components"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@vjs-10/html-icons": "workspace:*",
|
||||
"@vjs-10/html-media-elements": "workspace:*",
|
||||
"@vjs-10/html-media-store": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
export * from '@vjs-10/html-icons';
|
||||
export * from '@vjs-10/html-media-elements';
|
||||
export * from '@vjs-10/html-media-store';
|
||||
|
||||
export interface PlayerOptions {
|
||||
controls?: boolean;
|
||||
autoplay?: boolean;
|
||||
preload?: 'none' | 'metadata' | 'auto';
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export class VjsPlayer extends HTMLElement {
|
||||
private mediaElement: HTMLElement;
|
||||
private controlBar: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.mediaElement = document.createElement('vjs-media');
|
||||
this.controlBar = this.createControlBar();
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['src', 'controls', 'autoplay', 'preload', 'width', 'height'];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
||||
if (oldValue === newValue) return;
|
||||
|
||||
switch (name) {
|
||||
case 'src':
|
||||
case 'controls':
|
||||
case 'autoplay':
|
||||
case 'preload':
|
||||
this.mediaElement.setAttribute(name, newValue);
|
||||
break;
|
||||
case 'width':
|
||||
case 'height':
|
||||
this.updateDimensions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private render() {
|
||||
if (!this.shadowRoot) return;
|
||||
|
||||
const styles = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
background: #000;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.vjs-player-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vjs-control-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
:host(:hover) .vjs-control-bar {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vjs-play-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.vjs-progress-bar {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background: rgba(255,255,255,0.3);
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vjs-progress-fill {
|
||||
height: 100%;
|
||||
background: #ff0000;
|
||||
border-radius: 2px;
|
||||
width: 0%;
|
||||
transition: width 0.1s ease;
|
||||
}
|
||||
|
||||
.vjs-volume-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
const template = `
|
||||
<div class="vjs-player-container">
|
||||
${this.mediaElement.outerHTML}
|
||||
${this.getAttribute('controls') !== null ? this.controlBar.outerHTML : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.shadowRoot.innerHTML = styles + template;
|
||||
|
||||
const mediaEl = this.shadowRoot.querySelector('vjs-media');
|
||||
const controlBarEl = this.shadowRoot.querySelector('.vjs-control-bar');
|
||||
|
||||
if (mediaEl) {
|
||||
this.mediaElement = mediaEl as HTMLElement;
|
||||
}
|
||||
if (controlBarEl) {
|
||||
this.controlBar = controlBarEl as HTMLElement;
|
||||
}
|
||||
}
|
||||
|
||||
private createControlBar(): HTMLElement {
|
||||
const controlBar = document.createElement('div');
|
||||
controlBar.className = 'vjs-control-bar';
|
||||
|
||||
controlBar.innerHTML = `
|
||||
<button class="vjs-play-button">
|
||||
<vjs-icon name="play" size="20"></vjs-icon>
|
||||
</button>
|
||||
<div class="vjs-progress-bar">
|
||||
<div class="vjs-progress-fill"></div>
|
||||
</div>
|
||||
<button class="vjs-volume-button">
|
||||
<vjs-icon name="volumeUp" size="20"></vjs-icon>
|
||||
</button>
|
||||
`;
|
||||
|
||||
return controlBar;
|
||||
}
|
||||
|
||||
private setupEventListeners() {
|
||||
const playButton = this.shadowRoot?.querySelector('.vjs-play-button');
|
||||
const progressBar = this.shadowRoot?.querySelector('.vjs-progress-bar');
|
||||
const volumeButton = this.shadowRoot?.querySelector('.vjs-volume-button');
|
||||
|
||||
playButton?.addEventListener('click', () => {
|
||||
if (this.mediaElement && 'paused' in this.mediaElement) {
|
||||
if ((this.mediaElement as any).paused) {
|
||||
(this.mediaElement as any).play();
|
||||
} else {
|
||||
(this.mediaElement as any).pause();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.mediaElement?.addEventListener('vjs-play', () => {
|
||||
const icon = playButton?.querySelector('vjs-icon');
|
||||
if (icon) {
|
||||
(icon as any).name = 'pause';
|
||||
}
|
||||
});
|
||||
|
||||
this.mediaElement?.addEventListener('vjs-pause', () => {
|
||||
const icon = playButton?.querySelector('vjs-icon');
|
||||
if (icon) {
|
||||
(icon as any).name = 'play';
|
||||
}
|
||||
});
|
||||
|
||||
this.mediaElement?.addEventListener('vjs-timeupdate', (event: CustomEvent) => {
|
||||
const progressFill = this.shadowRoot?.querySelector('.vjs-progress-fill') as HTMLElement;
|
||||
if (progressFill && this.mediaElement && 'duration' in this.mediaElement) {
|
||||
const duration = (this.mediaElement as any).duration;
|
||||
const currentTime = event.detail.currentTime;
|
||||
const percentage = duration ? (currentTime / duration) * 100 : 0;
|
||||
progressFill.style.width = `${percentage}%`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateDimensions() {
|
||||
const width = this.getAttribute('width');
|
||||
const height = this.getAttribute('height');
|
||||
|
||||
if (width) {
|
||||
this.style.width = width.includes('px') ? width : `${width}px`;
|
||||
}
|
||||
if (height) {
|
||||
this.style.height = height.includes('px') ? height : `${height}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get('vjs-player')) {
|
||||
customElements.define('vjs-player', VjsPlayer);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user