Skip to content
·46 min read

ভিডিও মাস্টারি — পর্ব ৮: প্লেয়ার

নেটিভ HLS বনাম MSE বনাম Managed Media Source, বাফার ব্যবস্থাপনা, ABR অ্যালগরিদম (থ্রুপুট বনাম BOLA), স্টার্টআপের সিদ্ধান্ত, EME আর DRM, ক্যাপশন, CMCD, অটোপ্লে নীতি, আর ত্রুটি থেকে ফেরা।

নেটিভ HLS বনাম MSE বনাম Managed Media Source, বাফার ব্যবস্থাপনা, ABR অ্যালগরিদম (থ্রুপুট বনাম BOLA), স্টার্টআপের সিদ্ধান্ত, EME আর DRM, ক্যাপশন, CMCD, অটোপ্লে নীতি, আর ত্রুটি থেকে ফেরা।


সাত পর্বের কাজ এখানে এসে সত্যি হয়

এতক্ষণ আমরা সার্ভারের দিকটা বানিয়েছি — এনকোড, ল্যাডার, প্যাকেজিং, CDN, লাইভ। কিন্তু দর্শক এর কিছুই দেখে না।

দর্শক দেখে প্লেয়ার। আর প্লেয়ার তোমার সব ভালো কাজ নষ্ট করে দিতে পারে:

  • নিখুঁত পার-টাইটেল ল্যাডার বানালে, কিন্তু প্লেয়ার সবসময় সবচেয়ে নিচের ধাপ দিয়ে শুরু করলে — দর্শক ঝাপসা ছবি দেখবে
  • ৪ সেকেন্ডে ভিডিও প্রকাশ করলে, কিন্তু প্লেয়ার শুরু করতে ৬ সেকেন্ড নিলে — দর্শক ৬ সেকেন্ডই গুনবে
  • 4K এনকোড দিলে, কিন্তু প্লেয়ার ৩০০ পিক্সেল চওড়া বাক্সে চালালে — বিটগুলো পুরো নষ্ট

পর্ব ৬-এ Mux Data-র মেট্রিকগুলো মনে করো — startup time, rebuffer percentage, video quality score। তিনটাই প্লেয়ারের সিদ্ধান্তের সরাসরি ফল। তাই প্লেয়ার বোঝা মানে ওই সংখ্যাগুলো নিয়ন্ত্রণ করতে শেখা।


১. প্রথম বাস্তবতা: প্লেয়ার এক জিনিস না

“HTML5 ভিডিও চালানো” শুনতে একরকম, কিন্তু ভেতরে তিনটা আলাদা দুনিয়া আছে, আর কোনটায় আছো সেটা না জানলে ডিবাগ করা অসম্ভব।

নেটিভ HLS

Safari (macOS, iOS) আর বেশিরভাগ স্মার্ট টিভি HLS নিজেরাই বোঝে। তুমি শুধু .m3u8-এর URL একটা video ট্যাগে দিয়ে দিলেই চলবে — কোনো JavaScript লাইব্রেরি লাগে না।

সুবিধা: ব্যাটারি সাশ্রয়ী, ভালোভাবে অপ্টিমাইজ করা, পিকচার-ইন-পিকচার আর AirPlay নিজে থেকেই কাজ করে। অসুবিধা: তোমার নিয়ন্ত্রণ নেই। ABR-এর সিদ্ধান্ত, বাফারের আকার, কোন ধাপ থেকে শুরু — সবই অপারেটিং সিস্টেমের হাতে।

MSE — Media Source Extensions

ক্রোম, ফায়ারফক্স, এজ, ডেস্কটপ Safari-তে MSE আছে। এখানে ব্রাউজার ফাইলটা নিজে নামায় না — তোমার JavaScript সেগমেন্টগুলো নামায় আর একটা SourceBuffer-এ ঢেলে দেয়। hls.js, dash.js, Shaka Player — সবাই এটাই করে।

মানে ABR অ্যালগরিদম, বাফারের নীতি, ত্রুটি সামলানো — সব তোমার কোডে। এই কারণেই এই পর্বের বাকিটা সম্ভব।

একটা মজার ব্যাপার: HLS-এর পুরনো সেগমেন্টগুলো MPEG-TS ফরম্যাটে, কিন্তু MSE শুধু fMP4 বোঝে। তাই hls.js ব্রাউজারেই transmux করে — TS থেকে fMP4-এ রূপান্তর, চলার পথে। (পর্ব ১-এ বলা “কন্টেইনার আর কোডেক আলাদা জিনিস” — এটাই তার সবচেয়ে ব্যবহারিক প্রমাণ: ভেতরের H.264 ডেটা অপরিবর্তিত থাকে, শুধু মোড়কটা বদলায়।)

Managed Media Source — iPhone-এর গল্প

বহু বছর ধরে iPhone-এ MSE ছিল না। ফলে “আমরা DASH ব্যবহার করব” সিদ্ধান্ত নেওয়া দলগুলো iPhone-এ গিয়ে দেয়ালে ধাক্কা খেত।

কারণটা ইচ্ছাকৃত: MSE-তে নিয়ন্ত্রণ চলে যায় পেজের JavaScript-এর হাতে, আর পেজ ব্রাউজারের মতো করে জানে না কখন কী নামানো উচিত — ফলে ব্যাটারি খরচ নেটিভ HLS-এর চেয়ে অনেক বেশি হতো। অ্যাপল সেটা মানতে রাজি ছিল না।

সমাধান হিসেবে অ্যাপল Managed Media Source (MMS) বানায় — MSE-র ক্ষমতা, কিন্তু ব্রাউজারকে অনেক বেশি নিয়ন্ত্রণ ফিরিয়ে দিয়ে (ব্রাউজার বলতে পারে “এখন বাফার করা বন্ধ করো”)। MMS প্রথম আসে Safari 17.0-তে (iPad ও Mac, সেপ্টেম্বর ২০২৩), আর iPhone-এ Safari 17.1-এ, ২৫ অক্টোবর ২০২৩

তবু অ্যাপলের নিজের পরামর্শ — শুধু অ্যাপল ডিভাইসের জন্য বানালে নেটিভ HLS-ই ব্যবহার করো। hls.js-এ preferManagedMediaSource নামের একটা সেটিং আছে ঠিক এই পছন্দটা নিয়ন্ত্রণ করার জন্য।

ব্যবহারিক সিদ্ধান্ত: HLS দাও (সব জায়গায় চলে), Safari/iOS-এ নেটিভ প্লেব্যাক ব্যবহার করতে দাও, আর বাকি সব জায়গায় hls.js। DASH যোগ করো শুধু তখনই যখন সত্যিই দরকার — যেমন নির্দিষ্ট DRM বা ব্রডকাস্ট ইন্টিগ্রেশন।


২. বাফার — প্লেয়ারের আসল হৃদয়

প্লেয়ার ভিডিও “চালায়” না; সে আসলে একটা বাফার ভরে রাখে আর ডিকোডার সেখান থেকে খায়। প্লেয়ারের প্রায় সব সিদ্ধান্ত এই বাফারটার আকার নিয়ে।

hls.js-এর ডিফল্টগুলো দেখলে চিন্তাভাবনাটা পরিষ্কার হয়:

সেটিংডিফল্টমানে
maxBufferLength৩০ সেকেন্ডসামনে কতটা জমাবে (লক্ষ্য)
maxMaxBufferLength৬০০ সেকেন্ডসর্বোচ্চ সীমা
maxBufferSize৬০ MBসময় নয়, আকারের সীমা
backBufferLengthInfinityপিছনের কতটা রেখে দেবে
maxBufferHole০.১ সেকেন্ডকত ছোট ফাঁক টপকে যাওয়া যাবে

দুটো জিনিস লক্ষ করার মতো।

সময় আর আকার — দুটো সীমাই লাগে। ৩০ সেকেন্ড 240p-র ভিডিও কয়েক মেগাবাইট, কিন্তু ৩০ সেকেন্ড 4K কয়েকশো। শুধু সময় ধরে হিসাব করলে উঁচু ধাপে মেমরি শেষ হয়ে যাবে — বিশেষ করে সস্তা টিভি বা পুরনো ফোনে, যেখানে ভিডিও অ্যাপ ক্র্যাশ করার সবচেয়ে সাধারণ কারণ এটাই।

back buffer একটা আসল খরচ। দর্শক যা দেখে ফেলেছে সেটা মেমরিতে রেখে দিলে পিছনে টানলে সাথে সাথে চলে — কিন্তু মেমরি খরচ হয়। সীমিত ডিভাইসে backBufferLength কমানোই প্রথম চিকিৎসা।

ফাঁক আর আটকে যাওয়া

বাস্তব স্ট্রিমে বাফারে মাঝে মাঝে ছোট ছোট ফাঁক তৈরি হয় — একটা সেগমেন্ট শেষ হলো ১০.০০০ সেকেন্ডে, পরেরটা শুরু ১০.০২ সেকেন্ডে। ডিকোডার ওই ২০ মিলিসেকেন্ডে আটকে বসে থাকতে পারে, আর দর্শকের কাছে সেটা “ভিডিও হ্যাং করেছে”।

hls.js-এ এর জন্য আলাদা যন্ত্রপাতি আছে — maxBufferHole (কত ছোট ফাঁক টপকানো যাবে), nudgeOffset আর nudgeMaxRetry (আটকে গেলে প্লেহেডটা সামান্য ঠেলে দেওয়া), আর একটা পুরো gapController

এই ফাঁকগুলোর গোড়া প্রায় সবসময়ই এনকোডিংয়ের সময়ের সমস্যা — অসম সেগমেন্ট দৈর্ঘ্য, অডিও আর ভিডিও ট্র্যাকের দৈর্ঘ্যে অমিল, বা ভুল টাইমস্ট্যাম্প। মানে পর্ব ২-এর কি-ফ্রেম আর টাইমস্ট্যাম্পের শৃঙ্খলাটা এখানে এসে দর্শকের অভিজ্ঞতা হয়ে দাঁড়ায়।


৩. ABR — সিদ্ধান্তটা আসলে কীভাবে হয়

পর্ব ১-এ আমরা বলেছি প্লেয়ার “নেট দেখে ধাপ বাছে”। এবার আসল অ্যালগরিদমগুলো।

পদ্ধতি ১: থ্রুপুট দেখে

সরলতম নিয়ম: গত কয়েকটা সেগমেন্ট কত দ্রুত নামল মাপো, তারপর তার চেয়ে কম বিটরেটের সবচেয়ে উঁচু ধাপটা নাও।

সমস্যা দুটো, আর দুটোই সূক্ষ্ম:

মাপাটা মিথ্যা বলে। একটা সেগমেন্ট ক্যাশ থেকে এলে গতি অস্বাভাবিক বেশি দেখায়। তাই কাঁচা সংখ্যা ব্যবহার করা যায় না — গড় করতে হয়। hls.js দুটো EWMA (exponentially weighted moving average) রাখে, একটা দ্রুত আর একটা ধীর: VOD-এ abrEwmaFastVoD ৩.০ সেকেন্ড আর abrEwmaSlowVoD ৯.০ সেকেন্ড। দ্রুতটা হঠাৎ পরিবর্তন ধরে, ধীরটা স্থিতিশীলতা দেয়।

পুরোটা ব্যবহার করা যায় না। মাপা ব্যান্ডউইথের ঠিক সমান বিটরেট বাছলে সামান্য ওঠানামাতেই বাফার শেষ হবে। তাই সবাই একটা নিরাপত্তা গুণক রাখে — hls.js-এ abrBandWidthFactor ০.৯৫, dash.js-এ bandwidthSafetyFactor ০.৯।

আর উপরে ওঠার সময় নিয়ম কড়া: hls.js-এ abrBandWidthUpFactor ০.৭ — মানে উঁচু ধাপে যেতে হলে মাপা ব্যান্ডউইথের মাত্র ৭০% ব্যবহার করে হিসাব করা হয়। নিচে নামা দ্রুত, উপরে ওঠা সাবধানে। কারণটা অসামঞ্জস্যপূর্ণ শোনালেও ঠিক: ভুল করে নিচে নামলে ছবি একটু খারাপ হয়, ভুল করে উপরে উঠলে ভিডিও আটকে যায়।

পদ্ধতি ২: বাফার দেখে (BOLA)

সম্পূর্ণ আলাদা একটা চিন্তা: ব্যান্ডউইথ মাপার দরকারই নেই, বাফারে কতটুকু আছে সেটা দেখো।

বাফার ভরা থাকলে ঝুঁকি নেওয়া যায় — উঁচু ধাপে যাও। বাফার কমে আসছে মানে বিপদ — নিচে নামো। এর সবচেয়ে পরিচিত রূপ BOLA, আর dash.js-এ এটা BolaRule নামে আছে।

সুবিধা: থ্রুপুট মাপার সব মিথ্যা এড়িয়ে যায়। বাফারের পরিমাণ একটা সত্যিকারের, পর্যবেক্ষণযোগ্য সংখ্যা। অসুবিধা: শুরুতে বাফার তো খালিই — তখন এটা কিছুই জানে না।

বাস্তবে: দুটোই

তাই dash.js ডিফল্টে একটা dynamic কৌশল ব্যবহার করে, আর ওদের নিজের বর্ণনাটা চমৎকার পরিষ্কার: শুরু হয় ThroughputRule দিয়ে, আর বাফার টার্গেটে পৌঁছালে BOLA-তে চলে যায়।

মানে শুরুতে যখন বাফার খালি আর বাফারের তথ্য অর্থহীন, তখন থ্রুপুট; আর বাফার ভরে গেলে, যখন থ্রুপুটের চেয়ে বাফারই বেশি নির্ভরযোগ্য, তখন BOLA।

dash.js-এ মূল নিয়মগুলোর পুরো তালিকা: ThroughputRule, BolaRule, InsufficientBufferRule, SwitchHistoryRule, DroppedFramesRule, AbandonRequestsRule — আর কম লেটেন্সির জন্য আলাদা L2ARuleLoLPRule

শেষ তিনটা বিশেষভাবে দেখার মতো, কারণ এগুলো বাস্তব যন্ত্রণা থেকে জন্মেছে:

  • AbandonRequestsRule — একটা সেগমেন্ট নামতে অস্বাভাবিক দেরি হচ্ছে দেখলে মাঝপথে বাতিল করে নিচের ধাপ থেকে চাওয়া। নইলে প্লেয়ার একটা বিশাল সেগমেন্টের জন্য অপেক্ষা করতে করতে বাফার শেষ করে ফেলে।
  • DroppedFramesRule — নেট ঠিক আছে কিন্তু ডিভাইস ছবিটা ডিকোড করতে পারছে না। সস্তা টিভিতে 4K দিলে ঠিক এটাই হয়। hls.js-এও একই জিনিস আছে capLevelOnFPSDrop, fpsDroppedMonitoringThreshold হিসেবে।
  • SwitchHistoryRule — বারবার ধাপ বদলানো ঠেকানো। ঘন ঘন বদলানো নিজেই বিরক্তিকর।

সবচেয়ে সহজ জয়: প্লেয়ারের আকারের সাথে ধাপ বাঁধা

hls.js-এ capLevelToPlayerSize নামে একটা সেটিং আছে। চালু করলে প্লেয়ার তার নিজের আকারের চেয়ে বড় রেজলিউশন নামাবেই না

এটা কেন গুরুত্বপূর্ণ, পর্ব ৬-এর সাথে মিলিয়ে দেখো: Mux-এর Video Quality Score মাপে স্ট্রিমের রেজলিউশন বনাম প্লেয়ারের আকার। ৪০০ পিক্সেল চওড়া বাক্সে 1080p নামানো মানে ব্যান্ডউইথ আর ব্যাটারি — দুটোই পোড়ানো, অথচ দর্শকের চোখে কোনো লাভ নেই।

ABR — একটা সেগমেন্ট শেষ হওয়ার পর কী ঘটে

৪. স্টার্টআপ — সবচেয়ে গুরুত্বপূর্ণ একক সিদ্ধান্ত

পর্ব ৬-এ দেখা গেছে video startup time দর্শক ধরে রাখার সবচেয়ে প্রভাবশালী সংখ্যা, আর ২ সেকেন্ড পেরোলে উল্লেখযোগ্য দর্শক চলে যায়।

স্টার্টআপের সময় প্লেয়ারের একটা অস্বস্তিকর সমস্যা আছে: প্রথম সেগমেন্টটা কোন ধাপ থেকে নেবে? তখনো কোনো থ্রুপুটের তথ্য নেই, বাফারও খালি — মানে ABR-এর দুটো ইনপুটের একটাও নেই।

তিনটা পথ, তিনটাই বাস্তবে ব্যবহৃত:

  • সবচেয়ে নিচের ধাপ — দ্রুত শুরু হয়, কিন্তু প্রথম কয়েক সেকেন্ড ঝাপসা। যেহেতু অনেকে ঠিক ওই কয়েক সেকেন্ডেই সিদ্ধান্ত নেয় দেখবে কিনা, এটা ব্যয়বহুল।
  • একটা নির্দিষ্ট ধাপ ঠিক করে দেওয়া — hls.js-এ startLevel। তোমার দর্শকদের সাধারণ নেট জানা থাকলে এটাই প্রায়ই সবচেয়ে ভালো।
  • আগের সেশনের তথ্য মনে রাখা — hls.js-এ abrEwmaDefaultEstimate (ডিফল্ট ৫০০ kbps) দিয়ে প্রাথমিক অনুমানটা বসিয়ে দেওয়া যায়। আগেরবার দর্শক ৫ Mbps পেয়ে থাকলে সেটা localStorage-এ রেখে পরেরবার সেখান থেকেই শুরু করা যায়।

সাথে দুটো ছোট কিন্তু কার্যকর সেটিং: startFragPrefetch (ম্যানিফেস্ট পার্স হওয়ার আগেই প্রথম সেগমেন্ট আনা শুরু) আর testBandwidth

এখানে সার্ভারের দিকটাও মনে রাখো: startup time-এর একটা অংশ ম্যানিফেস্ট আনা। মাস্টার প্লেলিস্ট → মিডিয়া প্লেলিস্ট → init সেগমেন্ট → প্রথম সেগমেন্ট — চারটা পরপর রাউন্ড ট্রিপ। CDN-এ এগুলো গরম থাকা আর কাছে থাকাটা এখানেই দাম দেয়।


৫. DRM প্লেয়ারের দিক থেকে — EME

পর্ব ১ আর ৫-এ DRM দেখেছি সার্ভারের দিক থেকে। ব্রাউজারে এর নাম EME (Encrypted Media Extensions), আর প্রবাহটা এরকম:

  1. প্লেয়ার এনক্রিপ্ট করা সেগমেন্ট পায় আর দেখে এটার জন্য চাবি লাগবে
  2. প্লেয়ার ব্রাউজারের কাছে একটা key system চায় — Widevine, PlayReady বা FairPlay
  3. ব্রাউজার একটা license request তৈরি করে (এটা একটা অস্বচ্ছ বাইট-গুচ্ছ; তুমি এর ভেতরটা পড়তে পারবে না)
  4. তোমার কোড ওটা লাইসেন্স সার্ভারে পাঠায় — এখানেই তুমি ব্যবহারকারীর টোকেন জুড়ে দাও, অর্থাৎ অনুমোদনের সিদ্ধান্তটা তোমার
  5. উত্তরটা ব্রাউজারকে দিয়ে দাও; এরপর ডিকোডিং ঘটে এমন জায়গায় যেখানে তোমার JavaScript পৌঁছাতেই পারে না

hls.js-এ এর জন্য emeEnabled, drmSystems, drmSystemOptions আর licenseXhrSetup আছে — শেষটা ঠিক ওই টোকেন জোড়ার জায়গা।

দুটো ব্যবহারিক সত্য:

  • তিনটা key system মানে তিনটা আলাদা পথ। Widevine (ক্রোম, অ্যান্ড্রয়েড), PlayReady (এজ, উইন্ডোজ, অনেক টিভি), FairPlay (Safari, অ্যাপল)। সবখানে চলতে হলে তিনটার জন্যই প্যাকেজিং আর লাইসেন্স লাগে।
  • robustness আর HDCP-ই আসল যন্ত্রণা। কনটেন্টের মালিক প্রায়ই শর্ত দেন উঁচু রেজলিউশন শুধু হার্ডওয়্যার-স্তরের নিরাপত্তা থাকলে দেওয়া যাবে, আর মনিটরের কেবলেও HDCP লাগবে। ফলাফল: “আমার ল্যাপটপে HD চলে না” — অথচ কোড ঠিকঠাক। এটা বাগ না, নীতি।

আর পর্ব ১-এর কথাটা এখনো সত্যি: বেশিরভাগ কনটেন্টে DRM লাগে না। মেয়াদসহ সইকরা URL অনেক সস্তা আর অনেক কম ভঙ্গুর।


৬. ক্যাপশন — তিনটা আলাদা ব্যবস্থা

পর্ব ২-এ আমরা সাবটাইটেল ফাইল বানিয়েছি। প্লেয়ারের দিকে জিনিসটা তিন রকম হয়ে আসে, আর hls.js-এ তিনটার জন্য তিনটা আলাদা সুইচ আছে — enableWebVTT, enableIMSC1, enableCEA708Captions। এই তিনটা নাম চিনে রাখা দরকার:

  • WebVTT — ওয়েবের সাধারণ সাবটাইটেল ফরম্যাট, আলাদা ফাইল হিসেবে আসে। বেশিরভাগ ক্ষেত্রে এটাই তোমার পছন্দ।
  • IMSC1 — TTML-ভিত্তিক, XML-এ লেখা। সাজসজ্জা আর অবস্থান নিয়ন্ত্রণে অনেক বেশি ক্ষমতা, তাই ব্রডকাস্ট আর প্রিমিয়াম কনটেন্টে ব্যবহার হয় (যেখানে ক্যাপশনের চেহারা নিয়ে চুক্তিগত শর্ত থাকে)।
  • CEA-608/708 — ক্যাপশনটা ভিডিও স্ট্রিমের ভেতরেই লুকানো, আলাদা ফাইল না। এটা টেলিভিশনের উত্তরাধিকার। ব্যাপারটা গুরুত্বপূর্ণ, কারণ এই ক্যাপশন ট্রান্সকোডের সময় অসাবধানে মুছে যেতে পারে — আর তখন কেউ বুঝবেই না কী হারালো, যতক্ষণ না কোনো দর্শক অভিযোগ করেন।

সাইডকার (আলাদা ফাইল) বনাম ইন-ব্যান্ড (স্ট্রিমের ভেতরে) — এই পার্থক্যটাই মূল। সাইডকার সহজ: ভাষা যোগ করা মানে শুধু একটা ফাইল আর ম্যানিফেস্টে একটা লাইন, ভিডিও ছোঁয়ারই দরকার নেই।

আর একটা কথা যেটা প্রায়ই বাদ পড়ে: subtitle আর caption এক জিনিস না। subtitle ধরে নেয় তুমি শুনতে পাচ্ছ, শুধু ভাষাটা বোঝো না। caption ধরে নেয় তুমি শুনতে পাচ্ছ না — তাই দরজা বন্ধ হওয়ার শব্দ বা কে কথা বলছে, সেটাও লেখা থাকে। অ্যাক্সেসিবিলিটির দায়িত্বটা দ্বিতীয়টার।


৭. CMCD — প্লেয়ার আর CDN-এর মধ্যে কথা

এটা তুলনায় নতুন, আর অসাধারণ কাজের।

সমস্যাটা পুরনো: CDN দেখে শুধু HTTP রিকোয়েস্ট। সে জানে না ওই রিকোয়েস্টটা কার, তার বাফারে কতটুকু আছে, সে কি এইমাত্র শুরু করেছে নাকি আটকে যাওয়ার দ্বারপ্রান্তে। ফলে CDN আর প্লেয়ার একে অপরের অন্ধকারে কাজ করে।

CMCD (Common Media Client Data, স্ট্যান্ডার্ড CTA-5004) এর উত্তর: প্লেয়ার প্রতিটা রিকোয়েস্টের সাথে নিজের অবস্থার কিছু তথ্য পাঠায় — HTTP হেডারে বা query parameter হিসেবে।

কী পাঠায়: এনকোডেড বিটরেট, বাফারের দৈর্ঘ্য, মাপা থ্রুপুট, প্লেব্যাক রেট, আর তিনটা পরিচয় — content id, session id, device id

এটুকু জানা থাকলে যা যা সম্ভব হয়:

  • ডিবাগিং, যেটা আগে অসম্ভব ছিল — CDN-এর লগেই দেখা যায় কোন সেশনগুলো বাফার শূন্যের কাছে নিয়ে চলছিল
  • স্মার্ট prefetch — প্লেয়ার পরে কী চাইবে সেটা CDN আগেই আন্দাজ করতে পারে
  • অগ্রাধিকার — যে সেশনের বাফার প্রায় শেষ, তার রিকোয়েস্টকে আগে সেবা দেওয়া
  • content steering-এর সিদ্ধান্ত (পর্ব ৭) — আসল কোয়ালিটির ডেটা দেখে CDN বাছা

সবচেয়ে ভালো খবর: এটা চালু করা প্রায় বিনামূল্যে। hls.js-এ কনফিগে cmcd অবজেক্টে sessionId, contentId আর useHeaders দিলেই হয়; dash.js-এ cmcd: { enabled: true, ... }। ExoPlayer আর AVPlayer-সহ বড় প্লেয়ারগুলোতেও এখন সমর্থন আছে।


৮. অটোপ্লে — ব্রাউজারের নিয়ম

একটা ছোট জিনিস যেটা প্রায় সবাইকে একবার কামড়ায়।

ব্রাউজার শব্দসহ অটোপ্লে ঠেকিয়ে দেয়। নিয়মগুলো মোটামুটি:

  • শব্দ বন্ধ (muted) অটোপ্লে সবসময় চলে — এটাই একমাত্র নির্ভরযোগ্য পথ
  • শব্দসহ অটোপ্লে চলে যদি ব্যবহারকারী সাইটটার সাথে ইতিমধ্যে ইন্টারঅ্যাক্ট করে থাকেন, বা ডেস্কটপে তার Media Engagement Index যথেষ্ট উঁচু হয় (মানে তিনি আগে এই সাইটে শব্দসহ ভিডিও দেখেছেন), বা সাইটটা হোম স্ক্রিনে/PWA হিসেবে ইনস্টল করা থাকে

কোডে এর মানে একটাই: play() একটা Promise ফেরত দেয়, আর সেটা reject হতে পারে। ধরতে ভুলে গেলে ভিডিও চুপচাপ চলবে না, কোনো ত্রুটিও দেখাবে না।

video.play().catch(() => {
	// অটোপ্লে আটকে গেছে — একটা প্লে বোতাম দেখাও
	showPlayButton();
});

সবচেয়ে ভালো নকশা: muted অটোপ্লে দিয়ে শুরু করো, আর শব্দ চালু করার সুযোগটা স্পষ্টভাবে দেখাও। আর কন্ট্রোলগুলো সবসময় দেখাও, যাতে অটোপ্লে ব্যর্থ হলেও দর্শক নিজে চালাতে পারেন।


৯. ত্রুটি থেকে ফেরা

প্লেয়ারের কোডে সবচেয়ে অবহেলিত অংশ। hls.js-এর মডেলটা শেখার মতো, কারণ এটা ত্রুটিকে দুই ভাগে ভাগ করে।

Non-fatal — নিজে থেকেই সামলে যায়, তোমার কিছু করার নেই। একটা সেগমেন্ট নামতে ব্যর্থ হলো, আবার চেষ্টা করা হবে। এগুলোর জন্য নীতি নির্ধারিত আছে (fragLoadPolicy), যেখানে টাইমআউট, রিট্রাই সংখ্যা আর ব্যাকঅফ ঠিক করা — ঠিক যেমনটা পর্ব ৩-এ আমরা সার্ভারের সারির জন্য করেছিলাম।

Fatal — সিদ্ধান্ত তোমার। hls.js এখানে তিন রকম আচরণ সুপারিশ করে:

hls.on(Hls.Events.ERROR, function (name, data) {
	if (!data.fatal) return;

	switch (data.type) {
		case Hls.ErrorTypes.MEDIA_ERROR:
			hls.recoverMediaError(); // MediaSource রিসেট, শেষ অবস্থান থেকে আবার
			break;
		case Hls.ErrorTypes.NETWORK_ERROR:
			// নেটওয়ার্ক — সাধারণত আবার চেষ্টা বা দর্শককে জানানো
			break;
		default:
			hls.destroy();
			break;
	}
});

recoverMediaError() জিনিসটা বোঝা দরকার: এটা MediaSource-টা পুরো রিসেট করে আর শেষ জানা প্লেহেড অবস্থান থেকে আবার স্ট্রিম করা শুরু করে। ডিকোডার একটা খারাপ অবস্থায় আটকে গেলে এটাই বাঁচায়।

একটা সতর্কতা, যেটা hls.js-এর নিজের উদাহরণেই আছে: অসীম লুপে পড়ো না। ভাঙা স্ট্রিমে recover → আবার ত্রুটি → recover চলতেই থাকবে। তাই শেষ recover-এর সময় মনে রেখে (যেমন ৫ সেকেন্ডের মধ্যে আবার হলে থেমে যাওয়া) সীমা টানতে হয়।


১০. “এই টিভিতে খারাপ দেখাচ্ছে” — খোঁজার ক্রম

প্লেয়ারের অভিযোগগুলো প্রায় সবসময় একই কয়েকটা কারণে হয়। এই ক্রমে দেখলে দ্রুত পাওয়া যায়:

  1. আসলে কোন ধাপটা চলছিল? অনুমান করো না — প্লেয়ার থেকে বের করো। “খারাপ দেখাচ্ছে” মানে বেশিরভাগ সময় প্লেয়ার নিচের ধাপে আটকে আছে।
  2. প্লেয়ারের আকার কত ছিল? ছোট বাক্সে বড় স্ট্রিম, বা উল্টো। capLevelToPlayerSize এর অর্ধেক সমস্যা মিটিয়ে দেয়।
  3. ফ্রেম ড্রপ হচ্ছে কি? হলে সমস্যাটা নেটওয়ার্কের না, ডিভাইসের ডিকোডারের। কম রেজলিউশন বা কম fps-এ নামাও।
  4. ডিভাইসটা কি এই কোডেক/প্রোফাইল সত্যিই পারে? পর্ব ১-এর প্রোফাইল আর লেভেল এখানেই ফিরে আসে। শব্দ আসছে কিন্তু ছবি কালো — এটা প্রায় নিশ্চিতভাবে ডিকোড করতে না পারা।
  5. বাফার কি শুকিয়ে যাচ্ছে? হলে ABR ভুল সিদ্ধান্ত নিচ্ছে, বা সেগমেন্টগুলো খুব বড়।
  6. CMCD চালু করে CDN-এর লগে দেখো — অনুমানের বদলে ডেটা।

শেষ কথা — আট পর্বের পর

আটটা পর্বে আমরা যা করলাম:

পর্ব ১–৩ — ভিডিও কী, নিজের হাতে একটা ট্রান্সকোডার, আর সেটাকে প্রোডাকশন সিস্টেম বানানো। পর্ব ৪–৬ — নেটফ্লিক্স, Vimeo আর Mux কীভাবে করে, তাদের নিজেদের প্রকাশিত লেখা থেকে। পর্ব ৭–৮ — লাইভ, যেখানে সময় নেই; আর প্লেয়ার, যেখানে সব সিদ্ধান্তের ফল দর্শক আসলে দেখে।

শুরুতে বলেছিলাম ভিডিও ইঞ্জিনিয়ারিংয়ের কঠিন অংশটা কোডেকের গণিত না — কোডেকের কাজ ffmpeg করে দেয়। আটটা পর্ব পরে কথাটা আরও সত্যি মনে হয়। কঠিন অংশগুলো হলো সারি, অবস্থা, ব্যর্থতা, খরচ, ভাঙা ইনপুট, সময়ের সীমা, আর সবশেষে একটা ডিভাইস যেটা তোমার নিয়ন্ত্রণে নেই, একটা নেটওয়ার্কে যেটা তুমি দেখতে পাও না

আর সব সিদ্ধান্তের শেষে প্রশ্নটা একই — সমরকন্দে বাসে বসে থাকা মানুষটা প্লে চাপল, সে কি ভিডিওটা দেখতে পেল?


সূত্র