오디오 및 동영상 미리 로드로 빠른 재생

리소스를 적극적으로 미리 로드하여 미디어 재생을 가속화하는 방법

François Beaufort
François Beaufort

<ph type="x-smartling-placeholder">

재생이 빨라지면 더 많은 사용자가 동영상을 보거나 오디오입니다. 이는 알려진 사실입니다. 이 도움말에서는 오디오 및 동영상 재생의 속도를 높이는 데 사용할 수 있는 리소스를 미리 로드할 수 있습니다

출처: copyright Blender Foundation | www.blender.org 에서 확인할 수 있습니다.

미디어 파일을 미리 로드하는 세 가지 방법인 장점부터 설명하겠습니다. 그리고 단점이 있습니다

멋진데... 하지만...
동영상 미리 로드 속성 웹 서버에서 호스팅되는 고유한 파일에 간편하게 사용할 수 있습니다. 브라우저에서 이 속성을 완전히 무시할 수도 있습니다.
HTML 문서가 완전히 로드되면 리소스 가져오기가 시작되고 파싱할 수 있습니다.
앱이 미디어 소스 확장 프로그램 (MSE)에서 미디어 요소의 preload 속성을 무시함 MSE에 미디어를 제공합니다
링크 미리 로드 브라우저가 차단 없이 동영상 리소스를 요청하도록 강제합니다. 문서의 onload 이벤트 HTTP 범위 요청은 호환되지 않습니다.
MSE 및 파일 세그먼트와 호환됩니다. 전체 리소스를 가져올 때 작은 미디어 파일(5MB 미만)에만 사용해야 합니다.
수동 버퍼링 전체 제어 복잡한 오류 처리는 웹사이트의 책임입니다.

동영상 미리 로드 속성

동영상 소스가 웹 서버에서 호스팅되는 고유한 파일인 경우 동영상 preload 속성을 사용하여 브라우저에 힌트를 많은 정보나 콘텐츠를 미리 로드합니다. 즉, 미디어 소스 확장 프로그램은 (MSE)preload과 호환되지 않습니다.

리소스 가져오기는 초기 HTML 문서가 작성된 경우에만 시작됩니다. 완전히 로드 및 파싱됨 (예: DOMContentLoaded 이벤트 실행됨) 매우 다른 load 이벤트는 리소스가 확인할 수 있습니다

preload 속성을 metadata로 설정하면 사용자가 동영상이 필요하지만 메타데이터 (크기, 트랙 목록, 기간 등)을 지정하는 것이 바람직합니다. 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>

preload 속성을 auto로 설정하면 브라우저가 캐시할 수 있음을 나타냅니다. 충분한 데이터를 재생하기 전에 추가 버퍼링이 발생할 수 있습니다.

<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에서 다음과 같이 표시됩니다.

웹사이트에 동일한 도메인의 동영상 리소스가 많이 있는 경우 preload 값을 metadata로 설정하거나 poster를 정의하는 것이 좋습니다. 속성을 사용하고 preloadnone로 설정합니다. 이렇게 하면 동일한 도메인에 대한 최대 HTTP 연결 수 (6개의 HTTP 1.1 사양)이 있을 수 있으므로 리소스 로드가 지연될 수 있습니다. 또한 동영상이 핵심 사용자 환경의 일부가 아닌 경우 페이지 속도 향상

<ph type="x-smartling-placeholder">

다른 도움말에서 다룬 바와 같이 링크 미리 로드는 선언적 가져오기로 를 사용하면 페이지가 다운로드되고 있을 때 load 이벤트를 차단할 수 있습니다. 리소스 <link rel="preload">를 통해 로드된 데이터는 브라우저에 로컬로 저장되며 DOM, JavaScript 및 JavaScript에서 명시적으로 참조될 때까지 효과적으로 비활성됩니다. 또는 CSS 등이 있습니다.

미리 로드는 현재 탐색에 집중한다는 점에서 미리 가져오기와 다릅니다. 유형 (스크립트, 스타일, 글꼴, 동영상, 오디오 등) 현재 실행 중인 웹 서버에 대해 브라우저 캐시를 예열하는 데 세션.

전체 동영상 미리 로드

전체 동영상을 웹사이트에 미리 로드하여 JavaScript가 동영상 콘텐츠를 가져오도록 요청하는 경우 리소스로서 캐시에서 읽습니다. 브라우저에 의해 이미 캐시되었을 수 있습니다. 미리 로드 요청이 제대로 아직 완료되지 않으면 일반 네트워크 가져오기가 발생합니다.

<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>

지원

<ph type="x-smartling-placeholder">

다음을 사용하여 <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');
}

수동 버퍼링

Cache API와 서비스 워커에 대해 알아보기 전에 MSE로 동영상을 수동으로 버퍼링하는 방법 아래 예에서는 웹이 서버가 HTTP Range를 지원합니다. 이 작업은 세그먼트. Google의 Shaka와 같은 일부 미들웨어 라이브러리는 플레이어, 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개의 동영상이 포함된 웹페이지의 경우 동영상을 하나 가져오는 데 충분한 메모리가 세그먼트 파일을 만들었지만 숨겨진 <video>를 10개 생성해서는 안 됩니다. 요소 및 10개의 MediaSource 객체를 사용하고 해당 데이터를 제공하기 시작합니다.

아래의 두 부분으로 구성된 예는 Google Cloud의 강력하고 사용하기 쉬운 Cache API를 사용하여 동영상을 캐시할 수 있습니다. 참고로 IndexedDB로도 얻을 수 있습니다. 서비스 워커는 아직 window 객체에서도 Cache API에 액세스할 수 있습니다.

가져오기 및 캐시

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 객체는 아직 Range 응답을 지원하지 않기 때문입니다. CANNOT TRANSLATE 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;
    });

동영상 재생

사용자가 재생 버튼을 클릭하면 YouTube가 동영상의 첫 번째 세그먼트를 가능한 경우 즉시 재생이 시작되도록 Cache 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.
      });
    }
  });
}

서비스 워커로 Range 응답 만들기

이제 전체 동영상 파일을 가져와서 어떻게 해야 할까요? 브라우저가 HTTP Range 요청을 보낼 때는 Cache 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 파일 있습니다