بازپخش ویدیو وب تلفن همراه

François Beaufort
François Beaufort

چگونه بهترین تجربه رسانه تلفن همراه را در وب ایجاد می‌کنید؟ آسان! همه این‌ها به تعامل کاربر و اهمیتی که به رسانه در صفحه وب می‌دهید بستگی دارد. فکر می‌کنم همه ما موافقیم که اگر ویدیو دلیل اصلی بازدید کاربر باشد، تجربه کاربر باید فراگیر و تعاملی باشد.

بازپخش ویدیو در وب تلفن همراه

در این مقاله به شما نشان می‌دهم که چگونه تجربه رسانه‌ای خود را به‌صورت پیش‌رونده بهبود دهید و آن را به‌لطف تعداد زیادی از میاناهای برنامه‌سازی کاربردی وب، فراگیرتر کنید. به همین دلیل است که می‌خواهیم یک تجربه پخش‌کننده تلفن همراه ساده با کنترل‌های سفارشی، تمام‌صفحه و بازپخش پس‌زمینه بسازیم. اکنون می‌توانید نمونه را امتحان کنید و کد را در مخزن GitHub ما پیدا کنید.

کنترل‌های سفارشی

چیدمان HTML
شکل ۱.چیدمان HTML

همان‌طور که می‌بینید، چیدمان HTML که قرار است برای پخش‌کننده رسانه استفاده کنیم بسیار ساده است: یک عنصر ریشه <div> حاوی یک عنصر رسانه <video> و یک عنصر فرزند <div> اختصاص‌یافته به کنترل‌های ویدیو است.

کنترل‌های ویدیویی که بعداً به آن‌ها خواهیم پرداخت عبارتند از: دکمه پخش/مکث، دکمه تمام‌صفحه، دکمه‌های عقب و جلو بردن، و برخی عناصر برای زمان فعلی، مدت‌زمان، و ردیابی زمان.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls"></div>
</div>

خواندن فراداده ویدیو

ابتدا، بیایید منتظر بمانیم تا فراداده ویدیو بار شود تا مدت‌زمان ویدیو و زمان فعلی را تنظیم کنیم و نوار پیشرفت را مقداردهی اولیه کنیم. توجه داشته باشید که تابع secondsToTimeCode() یک تابع سودمندی سفارشی است که من نوشته‌ام و تعدادی ثانیه را به رشته‌ای در قالب «hh:mm:ss» تبدیل می‌کند که برای مورد ما مناسب‌تر است.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <strong>
      <div id="videoCurrentTime"></div>
      <div id="videoDuration"></div>
      <div id="videoProgressBar"></div>
    </strong>
  </div>
</div>
video.addEventListener('loadedmetadata', function () {
  videoDuration.textContent = secondsToTimeCode(video.duration);
  videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
  videoProgressBar.style.transform = `scaleX(${
    video.currentTime / video.duration
  })`;
});
فقط فراداده ویدیو
شکل ۲. پخش‌کننده رسانه که فراداده ویدیو را نشان می‌دهد

پخش/ مکث ویدیو

اکنون که فراداده ویدیو بار شده است، بیایید اولین دکمه‌مان را اضافه کنیم که به کاربر اجازه می‌دهد ویدیو را با video.play() و video.pause() بسته به وضعیت بازپخش آن پخش و موقتاً متوقف کند.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <strong><button id="playPauseButton"></button></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
playPauseButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (video.paused) {
    video.play();
  } else {
    video.pause();
  }
});

به‌جای تنظیم کنترل‌های ویدیو در شنونده رویداد click، از رویدادهای ویدیویی play و pause استفاده می‌کنیم. Making our controls events based helps with flexibility (as we'll see later with the Media Session API) and will allow us to keep our controls in sync if the browser intervenes in the playback. وقتی پخش ویدیو شروع می‌شود، وضعیت دکمه را به «مکث» تغییر می‌دهیم و کنترل‌های ویدیو را پنهان می‌کنیم. وقتی ویدیو موقتاً متوقف می‌شود، به‌سادگی وضعیت دکمه را به «پخش» تغییر می‌دهیم و کنترل‌های ویدیو را نشان می‌دهیم.

video.addEventListener('play', function () {
  playPauseButton.classList.add('playing');
});

video.addEventListener('pause', function () {
  playPauseButton.classList.remove('playing');
});

وقتی زمان نشان‌داده‌شده توسط مشخصه ویدیو currentTime ازطریق رویداد ویدیو timeupdate تغییر می‌کند، کنترل‌های سفارشی‌مان را نیز درصورت نمایان بودن به‌روز می‌کنیم.

video.addEventListener('timeupdate', function () {
  if (videoControls.classList.contains('visible')) {
    videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
    videoProgressBar.style.transform = `scaleX(${
      video.currentTime / video.duration
    })`;
  }
});

وقتی ویدیو تمام می‌شود، ما به‌سادگی وضعیت دکمه را به «پخش» تغییر می‌دهیم، ویدیو را currentTime به ۰ برمی‌گردانیم و فعلاً کنترل‌های ویدیو را نشان می‌دهیم. توجه داشته باشید که اگر کاربر نوعی ویژگی «پخش خودکار» را فعال کرده باشد، می‌توانیم انتخاب کنیم که ویدیو دیگری به‌طور خودکار بار شود.

video.addEventListener('ended', function () {
  playPauseButton.classList.remove('playing');
  video.currentTime = 0;
});

رفتن به عقب و جلو

بیایید ادامه دهیم و دکمه‌های «عقب بردن» و «جلو بردن» را اضافه کنیم تا کاربر بتواند به‌راحتی از برخی محتواها رد شود.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <button id="playPauseButton"></button>
    <strong
      ><button id="seekForwardButton"></button>
      <button id="seekBackwardButton"></button
    ></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
var skipTime = 10; // Time to skip in seconds

seekForwardButton.addEventListener('click', function (event) {
  event.stopPropagation();
  video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});

seekBackwardButton.addEventListener('click', function (event) {
  event.stopPropagation();
  video.currentTime = Math.max(video.currentTime - skipTime, 0);
});

همانند قبل، به‌جای تنظیم کردن سبک ویدیو در شنوندگان رویداد click این دکمه‌ها، از رویدادهای ویدیو seeking و seeked فعال‌شده برای تنظیم کردن روشنایی ویدیو استفاده خواهیم کرد. کلاس CSS سفارشی seeking من به سادگی filter: brightness(0); است.

video.addEventListener('seeking', function () {
  video.classList.add('seeking');
});

video.addEventListener('seeked', function () {
  video.classList.remove('seeking');
});

آنچه را که تاکنون ایجاد کرده‌ایم در زیر می‌بینید. در بخش بعدی، دکمه تمام‌صفحه را پیاده‌سازی خواهیم کرد.

تمام‌صفحه

در اینجا قصد داریم از چندین Web API برای ایجاد یک تجربه تمام‌صفحه عالی و یکپارچه استفاده کنیم. برای دیدن آن در عمل، نمونه را بررسی کنید.

بدیهی است که لازم نیست از همه آن‌ها استفاده کنید. کافی است مواردی را که برایتان منطقی است انتخاب کنید و آن‌ها را ترکیب کنید تا جریان سفارشی خودتان را ایجاد کنید.

جلوگیری از تمام‌صفحه خودکار

در iOS، عناصر video به‌طور خودکار وارد حالت تمام‌صفحه می‌شوند وقتی که بازپخش رسانه شروع می‌شود. As we're trying to tailor and control as much as possible our media experience across mobile browsers, I recommend you set the playsinline attribute of the video element to force it to play inline on iPhone and not enter fullscreen mode when playback begins. توجه داشته باشید که این کار هیچ عارضه جانبی‌ای روی مرورگرهای دیگر ندارد.

<div id="videoContainer"></div>
  <video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
  <div id="videoControls">...</div>
</div>

جابه‌جایی به حالت تمام‌صفحه با کلیک روی دکمه

Now that we prevent automatic fullscreen, we need to handle ourselves the fullscreen mode for the video with the Fullscreen API. وقتی کاربر روی «دکمه تمام‌صفحه» کلیک می‌کند، بیایید با document.exitFullscreen() از حالت تمام‌صفحه خارج شویم اگر حالت تمام‌صفحه درحال‌حاضر توسط سند استفاده می‌شود. درغیراین‌صورت، با روش requestFullscreen() (درصورت دردسترس بودن) درخواست تمام‌صفحه شدن در محفظه ویدیو کنید یا فقط در iOS به webkitEnterFullscreen() در عنصر ویدیو برگردید.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls">
    <button id="playPauseButton"></button>
    <button id="seekForwardButton"></button>
    <button id="seekBackwardButton"></button>
    <strong><button id="fullscreenButton"></button></strong>
    <div id="videoCurrentTime"></div>
    <div id="videoDuration"></div>
    <div id="videoProgressBar"></div>
  </div>
</div>
fullscreenButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (document.fullscreenElement) {
    document.exitFullscreen();
  } else {
    requestFullscreenVideo();
  }
});

function requestFullscreenVideo() {
  if (videoContainer.requestFullscreen) {
    videoContainer.requestFullscreen();
  } else {
    video.webkitEnterFullscreen();
  }
}

document.addEventListener('fullscreenchange', function () {
  fullscreenButton.classList.toggle('active', document.fullscreenElement);
});

تغییر به حالت تمام‌صفحه هنگام تغییر جهت صفحه

وقتی کاربر دستگاه را در حالت افقی می‌چرخاند، بیایید هوشمندانه عمل کنیم و به‌طور خودکار درخواست تمام‌صفحه کنیم تا تجربه‌ای فراگیر ایجاد شود. برای این کار، به Screen Orientation API نیاز داریم که هنوز در همه‌جا پشتیبانی نمی‌شود و در آن زمان هنوز در برخی مرورگرها پیشوند دارد. بنابراین، این اولین ارتقای پیش‌رو ما خواهد بود.

روش کار؟ به‌محض اینکه تغییر جهت صفحه‌نمایش را تشخیص دادیم، اگر پنجره مرورگر در حالت افقی باشد (یعنی عرض آن بیشتر از ارتفاع آن باشد)، درخواست تمام‌صفحه می‌کنیم. اگر نه، بیایید از حالت تمام‌صفحه خارج شویم. همین.

if ('orientation' in screen) {
  screen.orientation.addEventListener('change', function () {
    // Let's request fullscreen if user switches device in landscape mode.
    if (screen.orientation.type.startsWith('landscape')) {
      requestFullscreenVideo();
    } else if (document.fullscreenElement) {
      document.exitFullscreen();
    }
  });
}

قفل کردن صفحه در حالت افقی با کلیک روی دکمه

ازآنجایی‌که ویدیو ممکن است در حالت افقی بهتر دیده شود، ممکن است بخواهیم وقتی کاربر روی «دکمه تمام‌صفحه» کلیک می‌کند، صفحه را در حالت افقی قفل کنیم. برای اطمینان از اینکه این تجربه بهترین باشد، Screen Orientation API که قبلاً استفاده می‌شد و برخی پرس‌وجوهای رسانه را ترکیب خواهیم کرد.

قفل کردن صفحه‌نمایش در حالت افقی به‌آسانی تماس گرفتن با screen.orientation.lock('landscape') است. اما این کار را فقط زمانی باید انجام دهیم که دستگاه در حالت عمودی با matchMedia('(orientation: portrait)') باشد و بتوان آن را با یک دست با matchMedia('(max-device-width: 768px)') نگه داشت، زیرا این تجربه خوبی برای کاربران رایانه لوحی نخواهد بود.

fullscreenButton.addEventListener('click', function (event) {
  event.stopPropagation();
  if (document.fullscreenElement) {
    document.exitFullscreen();
  } else {
    requestFullscreenVideo();
    <strong>lockScreenInLandscape();</strong>;
  }
});
function lockScreenInLandscape() {
  if (!('orientation' in screen)) {
    return;
  }
  // Let's force landscape mode only if device is in portrait mode and can be held in one hand.
  if (
    matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches
  ) {
    screen.orientation.lock('landscape');
  }
}

باز کردن قفل صفحه هنگام تغییر جهت دستگاه

شاید متوجه شده باشید که تجربه صفحه قفلی که به‌تازگی ایجاد کرده‌ایم کامل نیست، زیرا وقتی صفحه قفل است، تغییرات جهت صفحه را دریافت نمی‌کنیم.

برای رفع این مشکل، بیایید از میانای برنامه‌سازی کاربردی جهت دستگاه استفاده کنیم، اگر دردسترس باشد. این میانای برنامه‌سازی کاربردی اطلاعاتی را از سخت‌افزار اندازه‌گیری موقعیت و حرکت دستگاه در فضا ارائه می‌دهد: ژیروسکوپ و قطب‌نمای دیجیتال برای جهت‌یابی آن، و شتاب‌سنج برای سرعت آن. وقتی تغییر جهت دستگاه را تشخیص دادیم، اگر کاربر دستگاه را در حالت عمودی نگه داشته باشد و صفحه در حالت افقی قفل شده باشد، قفل صفحه را با screen.orientation.unlock() باز می‌کنیم.

function lockScreenInLandscape() {
  if (!('orientation' in screen)) {
    return;
  }
  // Let's force landscape mode only if device is in portrait mode and can be held in one hand.
  if (matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches) {
    screen.orientation.lock('landscape')
    <strong>.then(function() {
      listenToDeviceOrientationChanges();
    })</strong>;
  }
}
function listenToDeviceOrientationChanges() {
  if (!('DeviceOrientationEvent' in window)) {
    return;
  }
  var previousDeviceOrientation, currentDeviceOrientation;
  window.addEventListener(
    'deviceorientation',
    function onDeviceOrientationChange(event) {
      // event.beta represents a front to back motion of the device and
      // event.gamma a left to right motion.
      if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
        previousDeviceOrientation = currentDeviceOrientation;
        currentDeviceOrientation = 'landscape';
        return;
      }
      if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
        previousDeviceOrientation = currentDeviceOrientation;
        // When device is rotated back to portrait, let's unlock screen orientation.
        if (previousDeviceOrientation == 'landscape') {
          screen.orientation.unlock();
          window.removeEventListener(
            'deviceorientation',
            onDeviceOrientationChange,
          );
        }
      }
    },
  );
}

همان‌طور که می‌بینید، این همان تجربه تمام‌صفحه یکپارچه‌ای است که به‌دنبالش بودیم. برای دیدن این ویژگی در عمل، نمونه را بررسی کنید.

بازپخش در پس‌زمینه

وقتی متوجه می‌شوید که صفحه وب یا ویدیویی در صفحه وب دیگر قابل‌مشاهده نیست، ممکن است بخواهید Analytics خود را به‌روز کنید تا این تغییر را منعکس کند. این امر همچنین می‌تواند بر بازپخش فعلی تأثیر بگذارد، مثلاً انتخاب قطعه‌ای دیگر، توقف موقت آن، یا حتی نمایش دکمه‌های سفارشی به کاربر.

توقف موقت ویدیو هنگام تغییر نمایان بودن صفحه

با Page Visibility API، می‌توانیم رؤیت‌پذیری فعلی صفحه را تعیین کنیم و از تغییرات رؤیت‌پذیری مطلع شویم. کد زیر ویدیو را وقتی صفحه پنهان است مکث می‌کند. این اتفاق زمانی روی می‌دهد که قفل صفحه فعال باشد یا مثلاً وقتی‌که بین زبانه‌ها جابه‌جا می‌شوید.

As most mobile browsers now offer controls outside of the browser that allow resuming a paused video, I recommend you set this behaviour only if user is allowed to play in the background.

document.addEventListener('visibilitychange', function () {
  // Pause video when page is hidden.
  if (document.hidden) {
    video.pause();
  }
});

نمایش/پنهان کردن دکمه صامت کردن هنگام تغییر رؤیت‌پذیری ویدیو

اگر از Intersection Observer API جدید استفاده می‌کنید، می‌توانید بدون هزینه، جزئیات بیشتری را مشاهده کنید. این میانای برنامه‌سازی کاربردی به شما اطلاع می‌دهد که عنصر مشاهده‌شده چه زمانی وارد نمای مرورگر می‌شود یا از آن خارج می‌شود.

بیایید دکمه صامت کردن را براساس رؤیت‌پذیری ویدیو در صفحه نشان دهیم/ پنهان کنیم. اگر ویدیو درحال پخش باشد اما درحال‌حاضر قابل‌مشاهده نباشد، دکمه صامت کردن کوچکی در گوشه پایین صفحه نشان داده می‌شود تا کاربر بتواند صدای ویدیو را کنترل کند. از رویداد ویدیویی volumechange برای به‌روزرسانی سبک دکمه صامت کردن استفاده می‌شود.

<button id="muteButton"></button>
if ('IntersectionObserver' in window) {
  // Show/hide mute button based on video visibility in the page.
  function onIntersection(entries) {
    entries.forEach(function (entry) {
      muteButton.hidden = video.paused || entry.isIntersecting;
    });
  }
  var observer = new IntersectionObserver(onIntersection);
  observer.observe(video);
}

muteButton.addEventListener('click', function () {
  // Mute/unmute video on button click.
  video.muted = !video.muted;
});

video.addEventListener('volumechange', function () {
  muteButton.classList.toggle('active', video.muted);
});

هر بار فقط یک ویدیو پخش کنید

اگر بیش‌از یک ویدیو در صفحه وجود دارد، پیشنهاد می‌کنم فقط یکی را پخش کنید و بقیه را به‌طور خودکار موقتاً متوقف کنید تا کاربر مجبور نباشد چند قطعه صوتی را به‌طور هم‌زمان بشنود.

// This array should be initialized once all videos have been added.
var videos = Array.from(document.querySelectorAll('video'));

videos.forEach(function (video) {
  video.addEventListener('play', pauseOtherVideosPlaying);
});

function pauseOtherVideosPlaying(event) {
  var videosToPause = videos.filter(function (video) {
    return !video.paused && video != event.target;
  });
  // Pause all other videos currently playing.
  videosToPause.forEach(function (video) {
    video.pause();
  });
}

سفارشی‌سازی اعلان‌های رسانه

با Media Session API، می‌توانید با ارائه فراداده برای ویدیویی که درحال پخش است، اعلان‌های رسانه را نیز سفارشی‌سازی کنید. همچنین به شما امکان می‌دهد رویدادهای مربوط به رسانه را مدیریت کنید، مانند جستجو یا تغییر قطعه که ممکن است از اعلان‌ها یا کلیدهای رسانه ناشی شود. برای دیدن این ویژگی در عمل، نمونه را بررسی کنید.

وقتی برنامه وب شما درحال پخش صدا یا ویدیو است، می‌توانید اعلان رسانه‌ای را که در سینی اعلان قرار دارد ببینید. در Android،‏ Chrome تمام تلاش خود را می‌کند تا بااستفاده از عنوان سند و بزرگ‌ترین تصویر نمادی که می‌تواند پیدا کند، اطلاعات مناسب را نشان دهد.

بیایید ببینیم چگونه می‌توان این اعلان رسانه را با تنظیم برخی فراداده‌های جلسه رسانه مانند عنوان، هنرمند، نام آلبوم، و کار هنری با Media Session API سفارشی‌سازی کرد.

playPauseButton.addEventListener('click', function(event) {
  event.stopPropagation();
  if (video.paused) {
    video.play()
    <strong>.then(function() {
      setMediaSession();
    });</strong>
  } else {
    video.pause();
  }
});
function setMediaSession() {
  if (!('mediaSession' in navigator)) {
    return;
  }
  navigator.mediaSession.metadata = new MediaMetadata({
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    album: 'Whenever You Need Somebody',
    artwork: [
      {src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
      {
        src: 'https://dummyimage.com/128x128',
        sizes: '128x128',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/192x192',
        sizes: '192x192',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/256x256',
        sizes: '256x256',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/384x384',
        sizes: '384x384',
        type: 'image/png',
      },
      {
        src: 'https://dummyimage.com/512x512',
        sizes: '512x512',
        type: 'image/png',
      },
    ],
  });
}

وقتی بازپخش تمام شد، لازم نیست جلسه رسانه را «رها کنید» چون اعلان به‌طور خودکار ناپدید می‌شود. به‌خاطر داشته باشید که navigator.mediaSession.metadata فعلی هنگام شروع هر بازپخش استفاده خواهد شد. به‌همین دلیل است که باید آن را به‌روز کنید تا مطمئن شوید همیشه اطلاعات مربوط را در اعلان رسانه نشان می‌دهید.

اگر برنامه وب شما فهرست پخش ارائه می‌دهد، ممکن است بخواهید به کاربر اجازه دهید مستقیماً از اعلان رسانه با نمادهای «قطعه قبلی» و «قطعه بعدی» در فهرست پخش شما پیمایش کند.

if ('mediaSession' in navigator) {
  navigator.mediaSession.setActionHandler('previoustrack', function () {
    // User clicked "Previous Track" media notification icon.
    playPreviousVideo(); // load and play previous video
  });
  navigator.mediaSession.setActionHandler('nexttrack', function () {
    // User clicked "Next Track" media notification icon.
    playNextVideo(); // load and play next video
  });
}

توجه داشته باشید که گرداننده‌های کنش رسانه باقی خواهند ماند. این بسیار شبیه به الگوی شنونده رویداد است، با این تفاوت که مدیریت رویداد به این معنی است که مرورگر هرگونه رفتار پیش‌فرض را متوقف می‌کند و از این به‌عنوان نشانه‌ای استفاده می‌کند که برنامه وب شما از کنش رسانه پشتیبانی می‌کند. بنابراین، کنترل‌های کنش رسانه نشان داده نخواهد شد مگر اینکه گرداننده کنش مناسب را تنظیم کنید.

ضمناً، لغو تنظیم گرداننده کنش رسانه به همان آسانی اختصاص دادن آن به null است.

Media Session API به شما امکان می‌دهد نمادهای اعلان رسانه «عقب بردن» و «جلو بردن» را نشان دهید اگر می‌خواهید مقدار زمان ردشده را کنترل کنید.

if ('mediaSession' in navigator) {
  let skipTime = 10; // Time to skip in seconds

  navigator.mediaSession.setActionHandler('seekbackward', function () {
    // User clicked "Seek Backward" media notification icon.
    video.currentTime = Math.max(video.currentTime - skipTime, 0);
  });
  navigator.mediaSession.setActionHandler('seekforward', function () {
    // User clicked "Seek Forward" media notification icon.
    video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
  });
}

نماد «پخش/توقف موقت» همیشه در اعلان رسانه نشان داده می‌شود و رویدادهای مربوطه به‌طور خودکار توسط مرورگر مدیریت می‌شوند. اگر به هر دلیلی رفتار پیش‌فرض کار نکرد، همچنان می‌توانید رویدادهای رسانه‌ای «پخش» و «مکث» را مدیریت کنید.

نکته جالب درباره Media Session API این است که سینی اعلان تنها جایی نیست که فراداده و کنترل‌های رسانه در آن قابل‌مشاهده است. اعلان رسانه به‌طور خودکار با هر دستگاه پوشیدنی جفت‌شده همگام‌سازی می‌شود. و همچنین در صفحه قفل نشان داده می‌شود.

بازخورد