VideoPlayer — پخشکنندهٔ ویدیو
پخشکنندهٔ ویدیوی TDS برای محتوایی که تصویر دارد: معرفی، آموزش، سخنرانی. صوت و پادکست با الگوی Podcast و پخشکنندهٔ داک انجام میشود، نه این کامپوننت.
وضعیت: beta · لایه: ۳ (کپی کد) · کد: کپی کد
import { VideoPlayer, openVideoOnDock } from '@tokapps/tuk/media';نمونهٔ زنده
ظاهر
پخشکننده بدون قاب است: نه حاشیه، نه سایه و نه گوشهٔ گرد در حالت درونصفحه. نوار کنترل روی خود کادر ویدیو قرار میگیرد، مثل یوتیوب:
- نوار پیشرفت نازک در لبهٔ پایین؛ دستگیره فقط با نشانگر دیده میشود.
- دکمههای پخش، صدا، روشنایی و شناورسازی با آیکون SVG اینلاین (همرنگ با متن نوار، بدون وابستگی به بستهٔ آیکون).
- کشوی صدا و روشنایی با نشانگر یا فوکوس باز میشود.
- نوار با حرکت نشانگر ظاهر میشود و در حال پخش پس از ۲٫۵ ثانیه بیحرکتی پنهان میشود؛ در مکث یا وقتی فوکوس دارد میماند.
منبع با کپی کد داده میشود. نوع آن از نشانی یا شیٔ منبع تشخیص داده میشود؛ نشانیهای نامعتبر یا ناامن هرگز بارگذاری نمیشوند و پیام خطا با کپی کد نشان داده میشود.
| منبع | ورودی | نکته |
|---|---|---|
| فایل مستقیم | کپی کد یا کپی کد | mp4، m4v، webm، ogv. فقط https یا مسیر هممبدا |
| یوتیوب | کپی کد، کپی کد، کپی کد، کپی کد | iframe با youtube-nocookie. کنترل از طریق postMessage |
| آپارات | کپی کد | iframe رسمی آپارات. کنترل داخلی با خود آپارات |
| استوریج | کپی کد | کپی کد نشانی امضاشدهٔ کوتاهعمر را از سرور شما میگیرد |
| رمزشده | کپی کد | قطعهقطعه با AES-GCM و MediaSource، بخش «ویدیوی رمزشده» |
نمونهٔ قابلکپی: فایل، یوتیوب و آپارات
import { VideoPlayer } from '@tokapps/tuk/media';<VideoPlayer source="https://cdn.example.com/media/intro.mp4" title="معرفی محصول" poster="/media/intro-poster.jpg" /><VideoPlayer source="https://youtu.be/aqz-KE-bpKQ" title="ویدیوی آموزشی یوتیوب" /><VideoPlayer source="https://www.aparat.com/v/abc123" title="ویدیوی آپارات" />نمونهٔ قابلکپی: استوریج با نشانی امضاشده
import { VideoPlayer } from '@tokapps/tuk/media';<VideoPlayer title="درس یک: مقدمه" source={{ kind: 'storage', key: 'courses/lesson-01.mp4', signUrl: async (key) => { const response = await fetch(`/api/media/sign?key=${encodeURIComponent(key)}`, { credentials: 'same-origin' }); const { url } = (await response.json()) as { url: string }; return url; }, }}/>کلید امضا هرگز در کلاینت نیست؛ کپی کد فقط نشانی امضاشده را برمیگرداند. کلید استوریج باید نسبی و بدون کپی کد باشد.
ژستها و کلیدها
ناحیهٔ ویدیو سه ژست دارد. هر ژست یک معادل کیبورد دارد، پس قرارداد دسترسپذیری برقرار است.
| ژست | اثر | معادل کیبورد (وقتی ناحیهٔ ویدیو فوکوس دارد) |
|---|---|---|
| کشیدن عمودی در لبهٔ چپ | روشنایی | Shift + ↑ / ↓ (گام ۵٪) |
| کشیدن عمودی در لبهٔ راست | صدای خود ویدیو | ↑ / ↓ (گام ۵٪) |
| کشیدن افقی در هر جا | جلو و عقب (کل عرض = ۱۲۰ ثانیه) | ← / → (۵ ثانیه)، Shift با ← / → (۳۰ ثانیه) |
| — | پخش و مکث | Space یا k |
| — | بیصدا | m |
| — | شناور / بازگرداندن | f |
نکتههای مهم:
- روشنایی با همپوشانی تیرهکنندهٔ روی ویدیو انجام میشود، چون مرورگر روشنایی صفحهٔ دستگاه را تغییر نمیدهد. کمترین مقدار ۱۵٪ است تا تصویر کاملاً سیاه نشود.
- صدا میزان کپی کد خود عنصر ویدیو است و مستقل از صدای سیستمعامل تنظیم میشود.
- جلو و عقب فقط برای منابع بومی (فایل، استوریج، رمزشده) کار میکند. در یوتیوب و آپارات ژست افقی غیرفعال است؛ زمان جاری در iframe قابل خواندن نیست.
- ژستها روی لبهها (یوتیوب و آپارات) یا روی کل ناحیه (منابع بومی) فعالاند؛ وسط iframe برای کلیکهای داخل سرویس آزاد میماند.
نمونهٔ قابلکپی: خاموش کردن یا محدود کردن ژستها
import { VideoPlayer } from '@tokapps/tuk/media';{/* بدون هیچ ژستی */}<VideoPlayer source="https://cdn.example.com/media/intro.mp4" title="بدون ژست" gestures={false} />{/* فقط جلو و عقب */}<VideoPlayer source="https://cdn.example.com/media/intro.mp4" title="فقط جلو و عقب" gestures={{ brightness: false, volume: false, seek: true }}/>حالت شناور
با دکمهٔ «شناور کردن»، کلید f یا کپی کد ویدیو به پنجرهٔ شناور در گوشهٔ صفحه میرود و بالای داک میماند. پخش قطع نمیشود، چون عنصر ویدیو جابهجا نمیشود، فقط موقعیتش تغییر میکند. پنجره با دستگیرهٔ بالایی یا فلشهای کیبورد (۱۶px در هر گام) جابهجا میشود.
import { useState } from 'react';import { VideoPlayer } from '@tokapps/tuk/media';export function FloatingIntro() { const [floating, setFloating] = useState(false); return ( <VideoPlayer source="https://cdn.example.com/media/intro.mp4" title="معرفی محصول" floating={floating} onFloatingChange={setFloating} /> );}برگهٔ داک
کپی کد ویدیو را در برگهٔ داک شیشهای باز میکند؛ همان سازوکار کپی کد. برگه داخل داک فیکس است، پس شناورسازی داخلی برای آن خاموش است. پخش با بستن برگه قطع میشود و با باز شدن دوباره از ابتدا شروع میشود.
import { openVideoOnDock } from '@tokapps/tuk/media';<button onClick={() => openVideoOnDock({ title: 'معرفی محصول', source: 'https://youtu.be/aqz-KE-bpKQ', model: 'liquid' })}> تماشای معرفی</button>ویدیوی رمزشده و قطعهقطعه
برای محتوای محدودشده، ویدیو به قطعههای جدا با AES-GCM بستهبندی میشود. مرورگر مانیفست را میگیرد، کلید را جدا از کنار همان دامنه دریافت میکند، هر قطعه را رمزگشایی و به کپی کد میدهد. فقط چند قطعهٔ جلوتر از زمان پخش دریافت میشود.
۱. مانیفست (کپی کد)
{ "version": 1, "mimeType": "video/mp4; codecs=\"avc1.42E01E, mp4a.40.2\"", "keyId": "lesson-01-k1", "duration": 30, "init": { "url": "/media/lesson-01/init.bin", "iv": "AAAAAAAAAAAAAAAA" }, "chunks": [ { "url": "/media/lesson-01/c0.bin", "iv": "AAAAAAAAAAAAAAAB", "start": 0, "duration": 10 }, { "url": "/media/lesson-01/c1.bin", "iv": "AAAAAAAAAAAAAAAC", "start": 10, "duration": 10 }, { "url": "https://cdn.example.com/media/lesson-01/c2.bin", "iv": "AAAAAAAAAAAAAAAD", "start": 20, "duration": 10 } ]}- کپی کد هر قطعه base64 با ۱۲ بایت و یکتا برای همان کلید است.
- کپی کد و کپی کد بر حسب ثانیهاند و کپی کد باید غیرنزولی باشد.
- کپی کد (اختیاری) سرآیند fMP4 است و قبل از همه append میشود.
- قطعهها باید fragment با زمان مطلق باشند (مثل قطعات DASH) تا کپی کد پشت هم بنشیند.
۲. کلید (کپی کد)
{ "keyId": "lesson-01-k1", "key": "BASE64_OF_16_OR_32_BYTES" }کلید ۱۶ بایت (AES-128) یا ۳۲ بایت (AES-256) است. شناسهٔ آن باید با کپی کد مانیفست یکی باشد. کلید باید از endpoint احراز هویتشدهٔ خودتان با کوکی هممبدا بیاید و هرگز در نشانی، bundle یا localStorage نباشد.
نمونهٔ قابلکپی
import { VideoPlayer } from '@tokapps/tuk/media';<VideoPlayer title="درس یک (رمزشده)" source={{ kind: 'encrypted', manifestUrl: '/media/lesson-01/manifest.json', keyUrl: '/api/media/lesson-01/key' }} tracks={[{ src: '/media/lesson-01/fa.vtt', srcLang: 'fa', label: 'فارسی', default: true }]}/>رمزنگاری قطعهها (ابزار بستهبندی)
import { encryptChunk, importChunkKey } from '@tokapps/tuk/media';const key = await importChunkKey(rawKeyBytes); // ۱۶ یا ۳۲ بایتconst iv = crypto.getRandomValues(new Uint8Array(12)); // یکتا برای هر قطعهconst cipher = await encryptChunk(key, iv, plainFragmentBuffer);ابزار بستهبندی (مثلاً ffmpeg و یک اسکریپت Node) باید IV را در مانیفست بنویسد و هرگز IV را دوباره استفاده نکند.
امنیت و محدودیتها
- آنچه این روش فراهم میکند: قطعهها در شبکه و در استوریج رمزشدهاند، کلید فقط برای کاربر احراز هویتشده است و هر قطعه با تگ AES-GCM یکپارچگیاش بررسی میشود.
- آنچه فراهم نمیکند: کاربری که ویدیو را پخش میکند میتواند با ضبط صفحه یا ابزارهای دیباگ مرورگر از آن نسخه بگیرد. این یک DRM واقعی نیست. برای حفاظت قویتر (Widevine، FairPlay، PlayReady) راهحل جدا با سرویس مجوز لازم است.
- مانیفست، کلید و قطعهها فقط از https یا مسیر هممبدا خوانده میشوند. نشانی با کپی کد، کپی کد، کپی کد، کپی کد یا اطلاعات ورود رد میشود.
- مرورگرهایی که کپی کد را برای MIME مانیفست رد کنند، خطای مشخص نشان میدهند.
زیرنویس
با کپی کد زیرنویس (WebVTT) برای منابع بومی اضافه میشود (WCAG 1.2.2). یوتیوب و آپارات زیرنویس خود را دارند و کپی کد برای آنها اثری ندارد.
دسترسپذیری
- ناحیهٔ ویدیو کپی کد با برچسب عنوان است؛ دکمهها و نوارها بومی و برچسبدار هستند؛ کپی کد وضعیت پخش، بیصدا و شناور را میدهد.
- نوار پیمایش و نوارهای صدا و روشنایی کپی کد دارند (مثلاً «۱:۳۵ از ۴:۰۰» یا «۶۰٪»).
- iframe یوتیوب و آپارات کپی کد دارد و خطاها در کپی کد اعلام میشوند.
- ویدیو هرگز خودکار با صدا شروع نمیشود و صدای خود کامپوننت رویداد صوتی UI ندارد.
- کاهش حرکت: انیمیشنهای کامپوننت فقط کپی کد و موقعیت جابهجایی دستیاند؛ انیمیشن ورود ندارد.
پیکربندی
| پراپ | نوع | پیشفرض | توضیح |
|---|---|---|---|
| کپی کد | کپی کد | — | منبع ویدیو (بخش منابع) |
| کپی کد | کپی کد | — | عنوان؛ برچسب ناحیه و iframe |
| کپی کد | کپی کد | — | تصویر پیش از پخش (منابع بومی) |
| کپی کد | کپی کد | کپی کد | زیرنویسهای WebVTT |
| کپی کد / کپی کد | کپی کد / کپی کد | کپی کد | حالت شناور (کنترلشده) |
| کپی کد | کپی کد | کپی کد | امکان شناورسازی؛ در برگهٔ داک کپی کد |
| کپی کد | کپی کد | کپی کد | سیاست ژستها |
| کپی کد | کپی کد (۰ تا ۱) | کپی کد | میزان صدای اولیه |
| کپی کد | کپی کد | کپی کد | ویدیو در کاغذ معنا ندارد |
| کپی کد | کپی کد | — | گزارش خطای بارگذاری یا پخش |
محدودیتهای فعلی
- یوتیوب و آپارات فقط از طریق iframe نمایش داده میشوند؛ جلو و عقب با ژست افقی و زمان جاری در آنها در دسترس نیست.
- روشنایی تیرهکننده است، نه روشنایی واقعی صفحهٔ دستگاه.
- حالت تمامصفحه از دکمهٔ داخلی ندارد؛ منابع بومی را میتوان با کپی کد روی ریشهٔ کامپوننت باز کرد.
- رمزنگاری قطعهها در مرورگر تأیید شده است، اما برای بستهبندی واقعی ویدیو (fMP4 با زمان مطلق) ابزار و نمونهای جدا باید به کار رود.