সক্রিয়ভাবে রিসোর্স প্রিলোড করে কীভাবে আপনার মিডিয়া প্লেব্যাককে ত্বরান্বিত করবেন।
দ্রুত প্লেব্যাক শুরু মানে আরও বেশি লোক আপনার ভিডিও দেখছে বা আপনার অডিও শুনছে। এটি একটি পরিচিত ঘটনা । এই নিবন্ধে আমি কৌশলগুলি অন্বেষণ করব যা আপনি আপনার ব্যবহারের ক্ষেত্রে নির্ভর করে সক্রিয়ভাবে রিসোর্স প্রিলোড করে আপনার অডিও এবং ভিডিও প্লেব্যাককে ত্বরান্বিত করতে ব্যবহার করতে পারেন।
আমি মিডিয়া ফাইলগুলি প্রিলোড করার তিনটি পদ্ধতি বর্ণনা করব, তাদের সুবিধা এবং অসুবিধাগুলি দিয়ে শুরু করে৷
এটা দারুণ... | কিন্তু... | |
---|---|---|
ভিডিও প্রিলোড বৈশিষ্ট্য | একটি ওয়েব সার্ভারে হোস্ট করা একটি অনন্য ফাইলের জন্য ব্যবহার করা সহজ৷ | ব্রাউজার সম্পূর্ণরূপে বৈশিষ্ট্য উপেক্ষা করতে পারে. |
HTML ডকুমেন্ট সম্পূর্ণরূপে লোড এবং পার্স করা হলে রিসোর্স ফেচিং শুরু হয়। | ||
মিডিয়া সোর্স এক্সটেনশন (MSE) মিডিয়া উপাদানগুলিতে preload বৈশিষ্ট্যকে উপেক্ষা করে কারণ অ্যাপটি MSE-কে মিডিয়া সরবরাহ করার জন্য দায়ী৷ | ||
লিঙ্ক প্রিলোড | ডকুমেন্টের onload ইভেন্ট ব্লক না করে একটি ভিডিও রিসোর্সের জন্য অনুরোধ করতে ব্রাউজারকে বাধ্য করে। | HTTP পরিসরের অনুরোধগুলি সামঞ্জস্যপূর্ণ নয়৷ |
MSE এবং ফাইল সেগমেন্টের সাথে সামঞ্জস্যপূর্ণ। | সম্পূর্ণ সম্পদ আনার সময় শুধুমাত্র ছোট মিডিয়া ফাইলের (<5 MB) জন্য ব্যবহার করা উচিত। | |
ম্যানুয়াল বাফারিং | সম্পূর্ণ নিয়ন্ত্রণ | জটিল ত্রুটি হ্যান্ডলিং ওয়েবসাইটের দায়িত্ব. |
ভিডিও প্রিলোড বৈশিষ্ট্য
যদি ভিডিও উত্সটি একটি ওয়েব সার্ভারে হোস্ট করা একটি অনন্য ফাইল হয়, তাহলে আপনি কতটা তথ্য বা বিষয়বস্তু প্রিলোড করতে হবে তা ব্রাউজারকে একটি ইঙ্গিত প্রদান করতে ভিডিও preload
বৈশিষ্ট্য ব্যবহার করতে চাইতে পারেন৷ এর মানে মিডিয়া সোর্স এক্সটেনশন (MSE) preload
সাথে সামঞ্জস্যপূর্ণ নয়৷
রিসোর্স ফেচিং তখনই শুরু হবে যখন প্রাথমিক HTML ডকুমেন্ট সম্পূর্ণভাবে লোড এবং পার্স করা হবে (যেমন DOMContentLoaded
ইভেন্টটি ফায়ার করা হয়েছে) যখন রিসোর্সটি আসলেই আনা হয়েছে তখন একেবারে ভিন্ন load
ইভেন্ট ফায়ার করা হবে।
metadata
preload
অ্যাট্রিবিউট সেট করা ইঙ্গিত দেয় যে ব্যবহারকারীর ভিডিওটির প্রয়োজন হবে বলে আশা করা হয় না, তবে এটির মেটাডেটা (মাত্রা, ট্র্যাক তালিকা, সময়কাল এবং আরও) আনা বাঞ্ছনীয়। মনে রাখবেন যে Chrome 64 থেকে শুরু করে, preload
জন্য ডিফল্ট মান হল metadata
। (এটা আগে auto
ছিল)।
<video id="video" preload="metadata" src="file.mp4" controls></video>
<script>
video.addEventListener('loadedmetadata', function() {
if (video.buffered.length === 0) return;
const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(`${bufferedSeconds} seconds of video are ready to play.`);
});
</script>
auto
preload
অ্যাট্রিবিউট সেট করা ইঙ্গিত দেয় যে ব্রাউজারটি পর্যাপ্ত ডেটা ক্যাশ করতে পারে যা আরও বাফারিংয়ের জন্য থামার প্রয়োজন ছাড়াই সম্পূর্ণ প্লেব্যাক সম্ভব।
<video id="video" preload="auto" src="file.mp4" controls></video>
<script>
video.addEventListener('loadedmetadata', function() {
if (video.buffered.length === 0) return;
const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(`${bufferedSeconds} seconds of video are ready to play.`);
});
</script>
যদিও কিছু সতর্কতা আছে। যেহেতু এটি শুধুমাত্র একটি ইঙ্গিত, ব্রাউজার সম্পূর্ণরূপে preload
বৈশিষ্ট্য উপেক্ষা করতে পারে। লেখার সময়, এখানে Chrome এ প্রয়োগ করা কিছু নিয়ম রয়েছে:
- যখন ডেটা সেভার সক্ষম করা হয়, তখন Chrome
preload
মানকে জোর করেnone
। - অ্যান্ড্রয়েড 4.3-এ, একটি অ্যান্ড্রয়েড বাগ- এর কারণে ক্রোম
preload
মানকে জোর করেnone
। - একটি সেলুলার সংযোগে (2G, 3G, এবং 4G), Chrome
metadata
preload
মানকে জোর করে।
টিপস
যদি আপনার ওয়েবসাইটে একই ডোমেনে অনেক ভিডিও রিসোর্স থাকে, তাহলে আমি আপনাকে metadata
preload
মান সেট করতে বা poster
অ্যাট্রিবিউটকে সংজ্ঞায়িত করার এবং none
preload
সেট না করার পরামর্শ দেব। এইভাবে, আপনি একই ডোমেনে (HTTP 1.1 স্পেস অনুসারে 6) সর্বাধিক সংখ্যক HTTP সংযোগগুলিকে আঘাত করা এড়াতে পারবেন যা সংস্থানগুলির লোডিং হ্যাং করতে পারে। মনে রাখবেন যে ভিডিওগুলি আপনার মূল ব্যবহারকারীর অভিজ্ঞতার অংশ না হলে এটি পৃষ্ঠার গতিও উন্নত করতে পারে৷
লিঙ্ক প্রিলোড
অন্যান্য নিবন্ধে কভার করা হয়েছে , লিঙ্ক প্রিলোড হল একটি ঘোষণামূলক আনয়ন যা আপনাকে ব্রাউজারকে load
ইভেন্টকে ব্লক না করে এবং পৃষ্ঠাটি ডাউনলোড করার সময় একটি সংস্থানের জন্য অনুরোধ করতে বাধ্য করতে দেয়। <link rel="preload">
এর মাধ্যমে লোড করা সংস্থানগুলি ব্রাউজারে স্থানীয়ভাবে সংরক্ষণ করা হয় এবং DOM, JavaScript বা CSS-এ স্পষ্টভাবে উল্লেখ না করা পর্যন্ত কার্যকরভাবে নিষ্ক্রিয় থাকে৷
প্রিলোড প্রিফেচ থেকে আলাদা যে এটি বর্তমান নেভিগেশনের উপর ফোকাস করে এবং তাদের প্রকারের (স্ক্রিপ্ট, স্টাইল, ফন্ট, ভিডিও, অডিও, ইত্যাদি) উপর ভিত্তি করে অগ্রাধিকার সহ সংস্থান আনে। এটি বর্তমান সেশনের জন্য ব্রাউজার ক্যাশে গরম করতে ব্যবহার করা উচিত।
সম্পূর্ণ ভিডিও প্রিলোড করুন
আপনার ওয়েবসাইটে কীভাবে একটি সম্পূর্ণ ভিডিও প্রিলোড করবেন তা এখানে রয়েছে যাতে আপনার জাভাস্ক্রিপ্ট যখন ভিডিও সামগ্রী আনতে বলে, তখন এটি ক্যাশে থেকে পড়া হয় কারণ সংস্থানটি ইতিমধ্যেই ব্রাউজার দ্বারা ক্যাশে করা হয়েছে৷ প্রিলোডের অনুরোধ এখনও শেষ না হলে, একটি নিয়মিত নেটওয়ার্ক আনয়ন ঘটবে৷
<link rel="preload" as="video" href="https://cdn.com/small-file.mp4">
<video id="video" controls></video>
<script>
// Later on, after some condition has been met, set video source to the
// preloaded video URL.
video.src = 'https://cdn.com/small-file.mp4';
video.play().then(() => {
// If preloaded video URL was already cached, playback started immediately.
});
</script>
যেহেতু প্রিলোড করা রিসোর্সটি উদাহরণে একটি ভিডিও উপাদান দ্বারা গ্রাস করা যাচ্ছে, as
লিঙ্কের মান হল video
। যদি এটি একটি অডিও উপাদান হয় তবে এটি as="audio"
হবে।
প্রথম সেগমেন্ট প্রিলোড করুন
নিচের উদাহরণে দেখানো হয়েছে কিভাবে <link rel="preload">
দিয়ে একটি ভিডিওর প্রথম সেগমেন্ট প্রিলোড করতে হয় এবং মিডিয়া সোর্স এক্সটেনশনের সাথে এটি ব্যবহার করতে হয়। আপনি যদি MSE JavaScript API এর সাথে পরিচিত না হন তবে MSE বেসিক দেখুন।
সরলতার জন্য, ধরে নেওয়া যাক পুরো ভিডিওটিকে file_1.webm
, file_2.webm
, file_3.webm
ইত্যাদির মতো ছোট ফাইলে বিভক্ত করা হয়েছে।
<link rel="preload" as="fetch" href="https://cdn.com/file_1.webm">
<video id="video" controls></video>
<script>
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
// If video is preloaded already, fetch will return immediately a response
// from the browser cache (memory cache). Otherwise, it will perform a
// regular network fetch.
fetch('https://cdn.com/file_1.webm')
.then(response => response.arrayBuffer())
.then(data => {
// Append the data into the new sourceBuffer.
sourceBuffer.appendBuffer(data);
// TODO: Fetch file_2.webm when user starts playing video.
})
.catch(error => {
// TODO: Show "Video is not available" message to user.
});
}
</script>
সমর্থন
আপনি নীচের স্নিপেটগুলির সাহায্যে <link rel=preload>
এর জন্য বিভিন্ন as
সমর্থন সনাক্ত করতে পারেন:
function preloadFullVideoSupported() {
const link = document.createElement('link');
link.as = 'video';
return (link.as === 'video');
}
function preloadFirstSegmentSupported() {
const link = document.createElement('link');
link.as = 'fetch';
return (link.as === 'fetch');
}
ম্যানুয়াল বাফারিং
আমরা ক্যাশে API এবং পরিষেবা কর্মীদের মধ্যে ডুব দেওয়ার আগে, আসুন দেখি কিভাবে MSE এর সাথে একটি ভিডিও ম্যানুয়ালি বাফার করা যায়। নীচের উদাহরণটি অনুমান করে যে আপনার ওয়েব সার্ভার HTTP Range
অনুরোধগুলিকে সমর্থন করে তবে এটি ফাইল বিভাগের সাথে বেশ অনুরূপ হবে। মনে রাখবেন কিছু মিডলওয়্যার লাইব্রেরি যেমন Google এর Shaka Player , JW Player , এবং Video.js আপনার জন্য এটি পরিচালনা করার জন্য তৈরি করা হয়েছে।
<video id="video" controls></video>
<script>
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
// Fetch beginning of the video by setting the Range HTTP request header.
fetch('file.webm', { headers: { range: 'bytes=0-567139' } })
.then(response => response.arrayBuffer())
.then(data => {
sourceBuffer.appendBuffer(data);
sourceBuffer.addEventListener('updateend', updateEnd, { once: true });
});
}
function updateEnd() {
// Video is now ready to play!
const bufferedSeconds = video.buffered.end(0) - video.buffered.start(0);
console.log(`${bufferedSeconds} seconds of video are ready to play.`);
// Fetch the next segment of video when user starts playing the video.
video.addEventListener('playing', fetchNextSegment, { once: true });
}
function fetchNextSegment() {
fetch('file.webm', { headers: { range: 'bytes=567140-1196488' } })
.then(response => response.arrayBuffer())
.then(data => {
const sourceBuffer = mediaSource.sourceBuffers[0];
sourceBuffer.appendBuffer(data);
// TODO: Fetch further segment and append it.
});
}
</script>
বিবেচনা
যেহেতু আপনি এখন সম্পূর্ণ মিডিয়া বাফারিং অভিজ্ঞতার নিয়ন্ত্রণে আছেন, আমি আপনাকে ডিভাইসের ব্যাটারি স্তর, "ডেটা-সেভার মোড" ব্যবহারকারীর পছন্দ এবং নেটওয়ার্ক তথ্য বিবেচনা করার পরামর্শ দিচ্ছি যখন প্রিলোড করার কথা ভাবছেন৷
ব্যাটারি সচেতনতা
একটি ভিডিও প্রিলোড করার কথা ভাবার আগে ব্যবহারকারীদের ডিভাইসের ব্যাটারি স্তর বিবেচনা করুন৷ পাওয়ার লেভেল কম হলে এটি ব্যাটারির আয়ু রক্ষা করবে।
ডিভাইসের ব্যাটারি ফুরিয়ে গেলে প্রিলোড অক্ষম করুন বা অন্তত একটি কম রেজোলিউশন ভিডিও প্রিলোড করুন।
if ('getBattery' in navigator) {
navigator.getBattery()
.then(battery => {
// If battery is charging or battery level is high enough
if (battery.charging || battery.level > 0.15) {
// TODO: Preload the first segment of a video.
}
});
}
"ডেটা-সেভার" সনাক্ত করুন
Save-Data
ক্লায়েন্ট ইঙ্গিত অনুরোধ শিরোনাম ব্যবহার করুন যারা তাদের ব্রাউজারে "ডেটা সেভিংস" মোডে অপ্ট-ইন করেছেন তাদের কাছে দ্রুত এবং হালকা অ্যাপ্লিকেশন সরবরাহ করতে। এই অনুরোধ শিরোনাম শনাক্ত করে, আপনার অ্যাপ্লিকেশন কাস্টমাইজ করতে পারে এবং একটি অপ্টিমাইজড ব্যবহারকারীর অভিজ্ঞতা প্রদান করতে পারে খরচ- এবং কর্মক্ষমতা-সীমাবদ্ধ ব্যবহারকারীদের কাছে।
আরও জানতে সেভ-ডেটা সহ দ্রুত এবং হালকা অ্যাপ্লিকেশন সরবরাহ করা দেখুন।
নেটওয়ার্ক তথ্যের উপর ভিত্তি করে স্মার্ট লোডিং
আপনি প্রিলোড করার আগে navigator.connection.type
চেক করতে চাইতে পারেন। যখন এটি cellular
সেট করা থাকে, আপনি প্রিলোডিং প্রতিরোধ করতে পারেন এবং ব্যবহারকারীদের পরামর্শ দিতে পারেন যে তাদের মোবাইল নেটওয়ার্ক অপারেটর ব্যান্ডউইথের জন্য চার্জ করতে পারে এবং শুধুমাত্র পূর্বে ক্যাশে করা সামগ্রীর স্বয়ংক্রিয় প্লেব্যাক শুরু করতে পারে৷
if ('connection' in navigator) {
if (navigator.connection.type == 'cellular') {
// TODO: Prompt user before preloading video
} else {
// TODO: Preload the first segment of a video.
}
}
নেটওয়ার্ক পরিবর্তনের সাথে সাথে কীভাবে প্রতিক্রিয়া জানাতে হয় তা শিখতে নেটওয়ার্ক তথ্যের নমুনাটি দেখুন।
একাধিক প্রথম সেগমেন্ট প্রাক-ক্যাশে
এখন যদি আমি অনুমানমূলকভাবে কিছু মিডিয়া বিষয়বস্তু প্রি-লোড করতে চাই তবে ব্যবহারকারী শেষ পর্যন্ত কোন মিডিয়ার অংশটি বেছে নেবে তা না জেনে? ব্যবহারকারী যদি এমন একটি ওয়েব পৃষ্ঠায় থাকে যাতে 10টি ভিডিও থাকে, তাহলে আমাদের কাছে সম্ভবত প্রতিটি থেকে একটি সেগমেন্ট ফাইল আনার জন্য যথেষ্ট মেমরি আছে তবে আমাদের অবশ্যই 10টি লুকানো <video>
উপাদান এবং 10টি MediaSource
অবজেক্ট তৈরি করা উচিত নয় এবং সেই ডেটা খাওয়ানো শুরু করা উচিত নয়।
নীচের দুটি-অংশের উদাহরণ আপনাকে দেখায় কিভাবে শক্তিশালী এবং সহজেই ব্যবহারযোগ্য ক্যাশে API ব্যবহার করে ভিডিওর একাধিক প্রথম অংশকে প্রাক-ক্যাশ করতে হয়। নোট করুন যে অনুরূপ কিছু IndexedDB এর সাথেও অর্জন করা যেতে পারে। আমরা এখনও পরিষেবা কর্মীদের ব্যবহার করছি না কারণ ক্যাশে API window
অবজেক্ট থেকে অ্যাক্সেসযোগ্য।
আনুন এবং ক্যাশে
const videoFileUrls = [
'bat_video_file_1.webm',
'cow_video_file_1.webm',
'dog_video_file_1.webm',
'fox_video_file_1.webm',
];
// Let's create a video pre-cache and store all first segments of videos inside.
window.caches.open('video-pre-cache')
.then(cache => Promise.all(videoFileUrls.map(videoFileUrl => fetchAndCache(videoFileUrl, cache))));
function fetchAndCache(videoFileUrl, cache) {
// Check first if video is in the cache.
return cache.match(videoFileUrl)
.then(cacheResponse => {
// Let's return cached response if video is already in the cache.
if (cacheResponse) {
return cacheResponse;
}
// Otherwise, fetch the video from the network.
return fetch(videoFileUrl)
.then(networkResponse => {
// Add the response to the cache and return network response in parallel.
cache.put(videoFileUrl, networkResponse.clone());
return networkResponse;
});
});
}
মনে রাখবেন যে আমি যদি HTTP Range
অনুরোধগুলি ব্যবহার করতে চাই তবে আমাকে ম্যানুয়ালি একটি Response
অবজেক্ট পুনরায় তৈরি করতে হবে কারণ ক্যাশে API এখনও Range
প্রতিক্রিয়া সমর্থন করে না। মনে রাখবেন যে networkResponse.arrayBuffer()
কল করলে রেন্ডারার মেমরিতে একবারে প্রতিক্রিয়ার পুরো বিষয়বস্তু পাওয়া যায়, এই কারণে আপনি ছোট পরিসর ব্যবহার করতে চাইতে পারেন।
রেফারেন্সের জন্য, আমি ভিডিও প্রিক্যাশে HTTP পরিসরের অনুরোধগুলি সংরক্ষণ করতে উপরের উদাহরণের অংশ পরিবর্তন করেছি।
...
return fetch(videoFileUrl, { headers: { range: 'bytes=0-567139' } })
.then(networkResponse => networkResponse.arrayBuffer())
.then(data => {
const response = new Response(data);
// Add the response to the cache and return network response in parallel.
cache.put(videoFileUrl, response.clone());
return response;
});
ভিডিও চালান
যখন একজন ব্যবহারকারী একটি প্লে বোতামে ক্লিক করেন, তখন আমরা ক্যাশে API-তে উপলব্ধ ভিডিওর প্রথম অংশটি আনব যাতে প্লেব্যাক উপলব্ধ হলে অবিলম্বে শুরু হয়। অন্যথায়, আমরা কেবল নেটওয়ার্ক থেকে এটি আনব। মনে রাখবেন যে ব্রাউজার এবং ব্যবহারকারীরা ক্যাশে সাফ করার সিদ্ধান্ত নিতে পারে।
যেমনটি আগে দেখা গেছে, আমরা MSE ব্যবহার করি ভিডিওর সেই প্রথম অংশকে ভিডিও উপাদানে ফিড করতে।
function onPlayButtonClick(videoFileUrl) {
video.load(); // Used to be able to play video later.
window.caches.open('video-pre-cache')
.then(cache => fetchAndCache(videoFileUrl, cache)) // Defined above.
.then(response => response.arrayBuffer())
.then(data => {
const mediaSource = new MediaSource();
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen, { once: true });
function sourceOpen() {
URL.revokeObjectURL(video.src);
const sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp09.00.10.08"');
sourceBuffer.appendBuffer(data);
video.play().then(() => {
// TODO: Fetch the rest of the video when user starts playing video.
});
}
});
}
পরিসেবা কর্মীর সাথে রেঞ্জ প্রতিক্রিয়া তৈরি করুন
এখন আপনি যদি একটি সম্পূর্ণ ভিডিও ফাইল নিয়ে আসেন এবং ক্যাশে এপিআইতে সংরক্ষণ করেন তবে কী হবে? যখন ব্রাউজারটি একটি HTTP Range
অনুরোধ পাঠায়, তখন আপনি অবশ্যই সম্পূর্ণ ভিডিওটিকে রেন্ডারার মেমরিতে আনতে চান না কারণ ক্যাশে API এখনও Range
প্রতিক্রিয়া সমর্থন করে না৷
তাই আমাকে দেখান কিভাবে এই অনুরোধগুলিকে আটকাতে হয় এবং একজন পরিষেবা কর্মী থেকে একটি কাস্টমাইজড Range
প্রতিক্রিয়া ফেরত দিতে হয়।
addEventListener('fetch', event => {
event.respondWith(loadFromCacheOrFetch(event.request));
});
function loadFromCacheOrFetch(request) {
// Search through all available caches for this request.
return caches.match(request)
.then(response => {
// Fetch from network if it's not already in the cache.
if (!response) {
return fetch(request);
// Note that we may want to add the response to the cache and return
// network response in parallel as well.
}
// Browser sends a HTTP Range request. Let's provide one reconstructed
// manually from the cache.
if (request.headers.has('range')) {
return response.blob()
.then(data => {
// Get start position from Range request header.
const pos = Number(/^bytes\=(\d+)\-/g.exec(request.headers.get('range'))[1]);
const options = {
status: 206,
statusText: 'Partial Content',
headers: response.headers
}
const slicedResponse = new Response(data.slice(pos), options);
slicedResponse.setHeaders('Content-Range': 'bytes ' + pos + '-' +
(data.size - 1) + '/' + data.size);
slicedResponse.setHeaders('X-From-Cache': 'true');
return slicedResponse;
});
}
return response;
}
}
এটা মনে রাখা গুরুত্বপূর্ণ যে আমি এই স্লাইসড রেসপন্সটি রিক্রিয়েট করার জন্য response.blob()
ব্যবহার করেছি কারণ এটি আমাকে ফাইলটিতে একটি হ্যান্ডেল দেয় যখন response.arrayBuffer()
সম্পূর্ণ ফাইলটিকে রেন্ডারার মেমরিতে নিয়ে আসে।
আমার কাস্টম X-From-Cache
HTTP শিরোনাম ব্যবহার করা যেতে পারে এই অনুরোধটি ক্যাশে থেকে এসেছে নাকি নেটওয়ার্ক থেকে এসেছে। নেটওয়ার্ক গতির একটি সূচক হিসাবে প্রতিক্রিয়া সময়কে উপেক্ষা করার জন্য এটি ShakaPlayer এর মতো একজন খেলোয়াড় ব্যবহার করতে পারে।
Range
অনুরোধগুলি কীভাবে পরিচালনা করা যায় তার সম্পূর্ণ সমাধানের জন্য অফিসিয়াল নমুনা মিডিয়া অ্যাপ এবং বিশেষ করে এর ranged-response.js ফাইলটি দেখুন।