스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
HLS 인터스티셜로 광고 경험 향상하기
HLS 인터스티셜을 활용하여 HLS 콘텐츠에 삽입한 광고가 매끄럽게 표시되게 하는 방법을 알아보세요. UI 경험을 조정하기 위해 통합 타임라인을 사용하는 방법과 인터스티셜을 위한 SharePlay를 빌드하는 방법도 공유합니다.
챕터
- 0:00 - Introduction
- 0:45 - Interstitals recap
- 1:56 - Integrated timeline
- 10:40 - SharePlay with interstitials
리소스
- Forum: Media Technologies
- Providing an integrated view of your timeline when playing HLS interstitials
관련 비디오
WWDC21
-
다운로드
안녕하세요, 여러분 AVFoundation 엔지니어 Julian입니다 최근 몇 년 동안 HLS 인터스티셜을 도입해 메인 콘텐츠에 간단히 광고를 삽입했습니다 올해에는 새로운 방법으로 광고를 제공하고 탐색할 수 있도록 인터스티셜을 제공합니다 광고를 포함하는 데 관심이 있거나 새로운 방식으로 HLS 인터스티셜을 포함하는 방법이 궁금하시다면 적합한 세션에 잘 찾아오셨습니다 현재 HLS 인터스티셜 이용 방법을 요약하는 것으로 시작하겠습니다
그런 다음 새로운 Integrated Timeline API를 살펴보겠습니다 이 API는 앱의 UI 경험을 빌드하기 위한 데이터 모델을 제공합니다
끝으로, Interstitial SharePlay의 새로운 기능을 살펴봅니다
HLS 인터스티셜을 빠르게 검토해 보겠습니다
HLS 인터스티셜에서 광고나 인터스티셜은 별개의 애셋이며 메인 콘텐츠 타임라인에서 예약할 수 있습니다 일반적인 미디어 플레이리스트의 모습입니다 콘텐츠의 5초 부분에 인터스티셜을 예약하려면 콘텐츠의 Program-Date-Time에서 Start-Date를 5초로 지정합니다 그러면 5초가 될 때까지 주된 콘텐츠가 재생된 다음 인터스티셜 URL 재생이 시작됩니다 최종적으로, 주된 콘텐츠 재생은 인터스티셜이 완료된 후 재개됩니다
HLS 인터스티셜은 메인 콘텐츠에 광고를 직접 삽입하는 것에 비해 많은 이점을 제공합니다 인터스티셜은 뒤늦게 구속력 있는 광고 결정을 할 수 있으며 광고를 주된 콘텐츠에 맞게 조정할 필요가 없습니다 HLS 인터스티셜은 주로 광고 삽입에 사용되었지만 다른 보조 콘텐츠를 제공하는 데 사용하는 것이 좋습니다 즉, 프로모션, 스튜디오 배너 프로그램 요약 세그먼트 등으로 단절된 형태로 제공되는 콘텐츠를 대체하는 것입니다 이것은 수박 겉핥기 식으로 콘텐츠에 HLS 인터스티셜 삽입하기를 설명한 것입니다 자세히 알아보려면 ’HLS에서 동적 프리롤 및 미드롤 살펴보기’를 참고하세요 올해에는 새로운 AVFoundation API인 Integrated Timeline을 제공합니다 이 API는 인터스티셜을 사용해 콘텐츠 재생의 타이밍과 시퀀스에 대한 데이터 모델을 도입합니다 이 데이터 모델을 활용하여 새로운 UI 경험을 빌드하고 사용자가 인터스티셜 안팎으로 원활하게 탐색하게 할 수 있습니다
시작으로, 통합 타임라인에 인터스티셜을 표시하는 다양한 방법의 개요를 살펴보겠습니다
이후 API와 API를 사용하는 방법을 알아보겠습니다 그런 다음 예제 앱에서 UI 빌드를 위해 통합 타임라인을 사용하는 방법을 살펴봅니다 마지막으로, 새로운 HLS 구문으로 인터스티셜이 통합 타임라인에서 어떻게 작동해야 할지 설명합니다
지금 바로 시작해 인터스티셜이 타임라인에 표시될 수 있는 방법을 몇 가지 살펴보죠
VOD 콘텐츠에 광고를 삽입하는 경우의 일반적인 사용 사례는 인터스티셜을 이동 막대의 한 지점으로 표시하는 것입니다
재생 시 해당 지점에 도달하면 재생헤드가 중지하면서 인터스티셜이 재생됩니다 인터스티셜이 종료되면 주된 콘텐츠가 다시 시작되어 재생헤드가 계속 진행합니다 인터스티셜의 또 다른 사용 사례는 인터스티셜을 이동 막대에서 인터스티셜의 실행 시간과 같은 범위로 표시하여 전체 이동 막대 실행 시간에 포함하는 것입니다
이 방법은 일반적으로 방송 스타일 라이브 스트림의 광고나 번인 콘텐츠를 인터스티셜로 대체하는 모든 곳에서 사용됩니다
이 마지막 예제는 이전 예제를 확장한 것입니다 이전 예제에서는 UI에서 인터스티셜을 노란색으로 강조한다고 명확하게 나타냈습니다 이 경우에는 인터스티셜이 주된 콘텐츠와 구분할 수 없게 통합됩니다 이것이 유용한 경우는 HLS 인터스티셜과 함께 평가 범퍼 프로그램 요약 같은 프리롤, 더빙 카드용 포스트롤 사용 시입니다 특히 주된 콘텐츠와 밀접하게 관련된 콘텐츠인 경우 적합합니다
통합 타임라인이 모델링할 다양한 사례를 살펴봤으니 각 사용 사례를 알리기 위해 AVPlayerInterstitalEvent API에 추가된 내용을 알아보겠습니다
타임라인의 한 지점을 알리기 위해 인터스티셜 이벤트를 만들고 timelineOccupancy라는 새로운 속성을 singlePoint 값을 사용해 설정할 수 있습니다
이번에는 타임라인의 범위를 알리기 위해 이벤트의 timelineOccupancy를 채우도록 설정할 수 있습니다 하지만 인터스티셜 이벤트는 재생헤드가 접근함에 따라 동적으로 로드되므로 이벤트의 실제 실행 시간이 늦게 파악될 수 있습니다 plannedDuration을 타임라인의 대안으로 제공하여 실제 실행 시간이 파악될 때까지 사용하도록 할 수 있습니다 이 속성을 범위 인터스티셜에 대해 설정하는 것이 모범 사례입니다
마지막 사례의 경우 범위 인터스티셜을 UI에서 구분할 수 없어야 하며 supplementsPrimaryContent를 true로 설정할 수 있습니다
타임라인에서 인터스티셜을 알릴 수 있는 방법을 살펴봤으니 이제 Integrated Timeline API를 알아보겠습니다
타임라인 진행을 관찰하기 위해 IntegratedTimeline은 currentTime 가져오기를 위한 API를 제공합니다
재생헤드가 PointEvent에 도달하면 currentTime의 진행이 중지되고 이벤트가 재생됩니다
이벤트 재생이 완료된 후 currentTime이 다시 진행됩니다 이 경우에는 다음 인터스티셜 이벤트에 도달하면 currentTime이 계속 진행되면서 이벤트가 재생됩니다
Integrated Timeline은 인터스티셜 내 탐색 기능도 제공합니다 예를 들면, 클라이언트가 10초만큼 앞으로 탐색하려고 합니다 이렇게 하면 이 FillEvent 한가운데에 이릅니다 이 API 없다면 이 탐색을 수행하기가 어려울 것입니다 특히 인터스티셜 이벤트가 현재 로드되지 않거나 AVPlayer의 대기 목록에 없기 때문입니다 하지만 이 API를 사용하면 타임라인의 모든 위치에 대해 임의로 탐색을 쉽게 수행할 수 있습니다 메인 콘텐츠이든, 인터스티셜이든 상관없습니다 Integrated Timeline의 일부로 AVPlayerItemSegment라는 새로운 객체를 제공합니다 타임라인의 각 영역은 세그먼트이며 각 세그먼트는 타임라인에서 해당 세그먼트가 점유하는 콘텐츠와 시간에 대한 주요 세부 정보를 제공합니다 주된 콘텐츠의 범위도 세그먼트로 간주됩니다
통합 타임라인의 가장 큰 이점 중 하나는 재생 UI를 쉽게 드로우할 수 있다는 것입니다 하지만 타임라인이 계속 변경되어 그렇게 하기가 어려울 수 있습니다 자체적으로 일관된 상태를 제공하기 위해 타임라인에서 제공할 수 있는 스냅샷 객체를 도입했습니다 스냅샷은 UI를 드로우하는 데 필요한, 자체적으로 일관된 속성의 전체 세트를 나타냅니다
이제 재생이 진행되고 타임라인이 변경되면서 하단의 스냅샷이 정적으로 유지되고 변형되지 않습니다
AVPlayerItemIntegratedSnapshot 사용 시 스냅샷의 모든 AVPlayerItemSegment 목록을 가져올 수 있습니다 이 예제에서는 2개의 세그먼트를 가져왔습니다 해당 세그먼트의 값을 살펴보겠습니다 예를 들어, 스냅샷의 첫 번째 Primary 세그먼트는 자신을 primary로 표시하고 타임라인의 0~5 범위를 점유합니다 다음 세그먼트는 인터스티셜인 PointEvent입니다 여기서 이 세그먼트는 5~5를 점유하며 실행 시간은 0입니다 또한 해당 인터스티셜 이벤트에 대한 액세스를 제공하여 timelineOccupancy를 singlePoint로 포함하기도 합니다
마지막으로, 선택된 마지막 세그먼트는 FillEvent입니다 이 세그먼트는 타임라인에서 20~30을 점유하며 채우기로 타임라인을 점유합니다
이제 모든 주요 객체를 이해했으니 Integrated Timeline을 사용해 UI를 빌드하는 방법을 살펴보죠 주된 AVPlayerItem을 생성하는 것으로 시작하겠습니다 그런 다음 AVPlayerItem에서 integratedTimeline을 가져오고 타임라인에서 스냅샷을 가져와 현재 상태를 드로우할 수 있습니다 UISlider를 드로우하는 루틴이 있고 시작, 종료, currentPosition만 있으면 된다고 가정해 보겠습니다 스냅샷에서 이러한 값을 모두 얻을 수 있습니다 start는 0이고, duration은 스냅샷의 실행 시간이며 currentPosition은 스냅샷의 currentTime입니다 이 시점에서는 간단한 이동 막대가 채우기 인터스티셜을 전체 슬라이더의 일부로 포함합니다 이제 타임라인에서 단일 지점 드로우를 시작합니다 지점 이벤트에 대한 위치를 가져오기 위해 스냅샷 세그먼트를 기준으로 필터링하고 유형이 인터스티셜이고 timelineOccupancy가 singlePoint인 세그먼트만 포함할 수 있습니다
이제 이러한 각 세그먼트에 대해 세그먼트의 timeMapping 대상 시작을 전체 타임라인의 해당 지점으로 사용하여 지점을 드로우합니다
마지막으로, 채우기 인터스티셜을 강조해 보겠습니다 여기서 스냅샷의 세그먼트를 필터링하고 점유 유형이 채우기인 인터스티셜을 나타내는 세그먼트를 포함합니다 보완하는 주된 콘텐츠에 이벤트가 UI에서 구분되어서는 안 된다고 지정되어 있으므로 해당 설정이 있는 모든 인터스티셜을 무시합니다 세그먼트가 확인되면 세그먼트의 timeMapping 대상을 사용하고 UI 슬라이더에서 해당 영역을 강조하면 됩니다
타임라인은 매우 동적이며 인터스티셜이 해결되면서 자주 변경되거나 라이브 스트림의 경우 빈번히 업데이트될 수 있으므로 snapshotsOutOfSyncNotification을 타임라인에서 수신하여 이러한 상황 발생 시 UI를 업데이트해야 합니다 알림 클로저가 호출되면 userInfo에서 이유를 확인할 수 있습니다 예를 들어 이유가 segmentsChanged인 경우 새 스냅샷을 사용해 이동 막대를 다시 드로우할 수 있습니다 하지만 이유가 currentSegmentChanged인 경우 playerControl이나 기타 UI 요소를 업데이트할 수 있습니다 인터스티셜로 전환하거나 이로부터 전환하려는 경우에요
API와 몇 가지 샘플 코드를 살펴봤으니 이 세션에 첨부된 예제 앱을 살펴보겠습니다
이 샘플 앱에 다양한 예제를 포함했습니다 Fill Interstitial이라는 예제를 살펴보겠습니다 이 예제에는 콘텐츠의 10초 부분에 예약된 범위 인터스티셜이 있죠
하단의 이동 막대에 10초 부분에서 시작되는 노란색 범위가 있는 인터스티셜을 보여 줍니다 재생 시, 예상 시간이 되면 재생이 인터스티셜로 전환됩니다
이제 탐색을 시도하고 주된 콘텐츠를 다시 탐색할 수 있습니다 마지막으로, 인터스티셜을 다시 탐색했습니다
멋지네요!
앞서 앱에서 인터스티셜을 구성하는 방법을 살펴봤지만 기본 플레이리스트에서 지정할 수도 있습니다 서버에서 타임라인상의 인터스티셜 등장을 어떻게 설명할 수 있는지 알아보죠
지점 예제로 시작하겠습니다 HLS 인터스티셜의 DateRange 태그입니다 타임라인에서 이를 POINT로 간주하도록 하기 위해 X-TIMELINE-OCCUPIES라는 새로운 속성을 도입했습니다 여기서 값은 POINT로 설정됩니다 또한 DateRange 태그에 새로운 속성을 포함하고 X-TIMELINE-STYLE에 HIGHLIGHT 값을 지정합니다 이는 클라이언트가 UI 표현에서 이 지점을 표시해야 함을 나타냅니다
범위 사례의 경우 새로운 X-TIMELINE-OCCUPIES 속성이 RANGE 값으로 설정됩니다 X-TIMELINE-STYLE은 이전 예제와 동일하게 유지됩니다
마지막으로, 인터스티셜을 UI에서 구분할 수 없게 표시하는 경우 X-TIMELINE-OCCUPIES는 RANGE로 유지됩니다 X-TIMELINE-STYLE이 PRIMARY로 설정되어 앱의 UI가 이를 고유하게 표시해서는 안 됨을 나타냅니다
Integrated Timeline에서는 인터스티셜이 UI의 주된 콘텐츠의 일부로 표시될 수 있으므로 이러한 인터스티셜이 SharePlay 세션 중에 조정될 수 있도록 하는 것도 좋습니다 따라서 올해에는 인터스티셜 조정에 대한 지원을 추가합니다
Interstitial SharePlay가 어떻게 작동하고 이를 어떻게 활성화할 수 있는지 살펴보겠습니다 조정된 SharePlay 세션에 두 플레이어가 있습니다 Player1과 Player2 모두 Event1 재생 중에 계속 재생을 동기화하려고 합니다
탐색의 경우, Player1이 Event1 외부를 탐색하고 주된 콘텐츠를 다시 탐색하려는 경우를 가정해 보죠 이 탐색은 Player2에게 제안되고 두 플레이어는 이 새로운 지점부터 계속 재생을 동기화합니다 인터스티셜에 SharePlay 지원을 활성화하기 위한 주요 요구 사항 중 하나는 인터스티셜 이벤트를 모든 참여자에게 공통되게 하는 겁니다 즉, 이 예제에서 Event1은 두 플레이어에게 공통되어야 하며 동일한 콘텐츠여야 합니다 인터스티셜이 공통되지 않은 경우 플레이어가 인터스티셜 중에 재생을 조정해서는 안 됩니다 이 요구 사항을 어떻게 알릴 수 있는지 살펴보겠습니다
AVPlayerInterstitialEvent에 새로 추가된 기능 중 하나는 콘텐츠가 참여자 또는 재생 세션 전체에 걸쳐 다를 수 있는지 여부를 지정하는 부울 값입니다 이 예제에서는 contentMayVary를 false로 설정하면 인터스티셜이 정적이거나 공통임을 알리게 되며 이 이벤트가 조정될 수 있게 됩니다
마지막으로, 새로운 속성 X-CONTENT-MAY-VARY를 사용하고 NO로 설정해 DateRange 태그에서 동일한 동작을 알릴 수도 있습니다
이제 SharePlay를 지원하는 샘플 앱을 살펴보겠습니다 FaceTime 통화에 참여하는 두 대의 휴대폰을 설정했습니다 이 데모에서는 Fill (Supplements Primary) 예제를 선택합니다 이 예제는 콘텐츠의 10초 부분에 인터스티셜을 포함합니다
두 휴대폰이 모두 동기화되어 시작되고 인터스티셜로 전환될 때 계속 동기화됩니다 그런 다음 일시 정지를 실행한 다음 뒤로 탐색을 수행하고 마지막으로 앞으로 탐색을 합니다
모든 명령이 어떻게 두 기기에서 매끄럽게 작동하는지 확인하세요 마무리하자면 이제 Integrated Timeline을 사용해 인터스티셜이 포함된 UI 경험을 빌드할 수 있습니다 동작을 지정하여 인터스티셜 경험을 맞춤화할 수 있습니다 API에서 지정하거나 새로운 DateRange 속성을 사용하면 됩니다 이제 인터스티셜 애셋에 대한 SharePlay 지원을 활성화할 수도 있습니다 API에서 인터스티셜을 가변이 아닌 것으로 표시하면 됩니다 다른 관련 세션을 확인하려면 ’HLS에서 동적 프리롤 및 미드롤 살펴보기’에서 HLS 인터스티셜에 대해 자세히 알아보세요 SharePlay에 대해 자세히 알아보려면 ’Group Activities로 미디어 경험 조정하기’를 참고하세요 시청해 주셔서 감사합니다
-
-
3:55 - Create a point interstitial event
// Creating a single point interstitial event let pointEvent = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) pointEvent.timelineOccupancy = .singlePoint
-
4:07 - Create a fill interstitial event
// Creating a fill interstitial event let fillEvent = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) fillEvent.timelineOccupancy = .fill fillEvent.plannedDuration = CMTime(value: 15, timescale: 1)
-
4:32 - Create fill interstitial supplementing primary
// Creating a fill interstitial event supplementing primary let fillEvent2 = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) fillEvent2.supplementsPrimaryContent = true fillEvent2.timelineOccupancy = .fill fillEvent2.plannedDuration = CMTime(value: 15, timescale: 1)
-
7:14 - Draw simple transport bar with integrated timeline
// Create AVPlayerItem and obtain its integrated timeline let item = AVPlayerItem(url: ...) let integratedTimeline = item.integratedTimeline // Any time we need a new representation of the timeline state, we can request for a snapshot let snapshot = integratedTimeline.currentSnapshot // Using our snapshot, we can build a simple transport bar drawUISlider(start: .zero, duration: snapshot.duration, currentPosition: snapshot.currentTime) // Draw single-point interstitials on the transport bar let pointSegments = snapshot.segments.filter { segment in segment.segmentType == .interstitial && segment.interstitialEvent?.timelineOccupancy == .singlePoint } for segment in pointSegments { drawPoint(position: segment.timeMapping.target.start) } // Draw range interstitials on the transport bar let highlightFillSegments = snapshot.segments.filter { segment in if (segment.segmentType == .interstitial) { if let interstitialEvent = segment.interstitialEvent { return interstitialEvent.timelineOccupancy == .fill && !interstitialEvent.supplementsPrimaryContent } } return false } for segment in highlightFillSegments { let range = segment.timeMapping.target highlightRegion(start: range.start, end: range.end) }
-
8:26 - Listen to snapshotsOutOfSyncNotification to update our UI
// Listen to integrated timeline notifications to update our logic for await _ in NotificationCenter.default.notifications(named: AVPlayerItemIntegratedTimeline.snapshotsOutOfSyncNotification, object: integratedTimeline) { let reason = _.userInfo![AVPlayerItemIntegratedTimeline.snapshotsOutOfSyncReasonKey] as! AVPlayerIntegratedTimelineSnapshotsOutOfSyncReason switch(reason) { case .segmentsChanged: redrawTransportBar(snapshot: integratedTimeline.currentSnapshot) case .currentSegmentChanged: updatePlayerControls(snapshot: integratedTimeline.currentSnapshot) } }
-
11:42 - Set ContentMayVary to false for Interstitial SharePlay support
// Set contentMayVary to false for SharePlay support let event = AVPlayerInterstitialEvent(primaryItem: playerItem, time: ten) event.contentMayVary = false event.timelineOccupancy = .fill event.plannedDuration = CMTime(value: 15, timescale: 1) event.supplementsPrimaryContent = truensert code snippet.
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.