1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.ext.cast; 17 18 import android.util.SparseArray; 19 import android.util.SparseIntArray; 20 import androidx.annotation.Nullable; 21 import com.google.android.exoplayer2.C; 22 import com.google.android.exoplayer2.Timeline; 23 import java.util.Arrays; 24 25 /** 26 * A {@link Timeline} for Cast media queues. 27 */ 28 /* package */ final class CastTimeline extends Timeline { 29 30 /** Holds {@link Timeline} related data for a Cast media item. */ 31 public static final class ItemData { 32 33 /** Holds no media information. */ 34 public static final ItemData EMPTY = new ItemData(); 35 36 /** The duration of the item in microseconds, or {@link C#TIME_UNSET} if unknown. */ 37 public final long durationUs; 38 /** 39 * The default start position of the item in microseconds, or {@link C#TIME_UNSET} if unknown. 40 */ 41 public final long defaultPositionUs; 42 /** Whether the item is live content, or {@code false} if unknown. */ 43 public final boolean isLive; 44 ItemData()45 private ItemData() { 46 this( 47 /* durationUs= */ C.TIME_UNSET, /* defaultPositionUs */ 48 C.TIME_UNSET, 49 /* isLive= */ false); 50 } 51 52 /** 53 * Creates an instance. 54 * 55 * @param durationUs See {@link #durationsUs}. 56 * @param defaultPositionUs See {@link #defaultPositionUs}. 57 * @param isLive See {@link #isLive}. 58 */ ItemData(long durationUs, long defaultPositionUs, boolean isLive)59 public ItemData(long durationUs, long defaultPositionUs, boolean isLive) { 60 this.durationUs = durationUs; 61 this.defaultPositionUs = defaultPositionUs; 62 this.isLive = isLive; 63 } 64 65 /** 66 * Returns a copy of this instance with the given values. 67 * 68 * @param durationUs The duration in microseconds, or {@link C#TIME_UNSET} if unknown. 69 * @param defaultPositionUs The default start position in microseconds, or {@link C#TIME_UNSET} 70 * if unknown. 71 * @param isLive Whether the item is live, or {@code false} if unknown. 72 */ copyWithNewValues(long durationUs, long defaultPositionUs, boolean isLive)73 public ItemData copyWithNewValues(long durationUs, long defaultPositionUs, boolean isLive) { 74 if (durationUs == this.durationUs 75 && defaultPositionUs == this.defaultPositionUs 76 && isLive == this.isLive) { 77 return this; 78 } 79 return new ItemData(durationUs, defaultPositionUs, isLive); 80 } 81 } 82 83 /** {@link Timeline} for a cast queue that has no items. */ 84 public static final CastTimeline EMPTY_CAST_TIMELINE = 85 new CastTimeline(new int[0], new SparseArray<>()); 86 87 private final SparseIntArray idsToIndex; 88 private final int[] ids; 89 private final long[] durationsUs; 90 private final long[] defaultPositionsUs; 91 private final boolean[] isLive; 92 93 /** 94 * Creates a Cast timeline from the given data. 95 * 96 * @param itemIds The ids of the items in the timeline. 97 * @param itemIdToData Maps item ids to {@link ItemData}. 98 */ CastTimeline(int[] itemIds, SparseArray<ItemData> itemIdToData)99 public CastTimeline(int[] itemIds, SparseArray<ItemData> itemIdToData) { 100 int itemCount = itemIds.length; 101 idsToIndex = new SparseIntArray(itemCount); 102 ids = Arrays.copyOf(itemIds, itemCount); 103 durationsUs = new long[itemCount]; 104 defaultPositionsUs = new long[itemCount]; 105 isLive = new boolean[itemCount]; 106 for (int i = 0; i < ids.length; i++) { 107 int id = ids[i]; 108 idsToIndex.put(id, i); 109 ItemData data = itemIdToData.get(id, ItemData.EMPTY); 110 durationsUs[i] = data.durationUs; 111 defaultPositionsUs[i] = data.defaultPositionUs == C.TIME_UNSET ? 0 : data.defaultPositionUs; 112 isLive[i] = data.isLive; 113 } 114 } 115 116 // Timeline implementation. 117 118 @Override getWindowCount()119 public int getWindowCount() { 120 return ids.length; 121 } 122 123 @Override getWindow(int windowIndex, Window window, long defaultPositionProjectionUs)124 public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) { 125 long durationUs = durationsUs[windowIndex]; 126 boolean isDynamic = durationUs == C.TIME_UNSET; 127 return window.set( 128 /* uid= */ ids[windowIndex], 129 /* tag= */ ids[windowIndex], 130 /* manifest= */ null, 131 /* presentationStartTimeMs= */ C.TIME_UNSET, 132 /* windowStartTimeMs= */ C.TIME_UNSET, 133 /* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET, 134 /* isSeekable= */ !isDynamic, 135 isDynamic, 136 isLive[windowIndex], 137 defaultPositionsUs[windowIndex], 138 durationUs, 139 /* firstPeriodIndex= */ windowIndex, 140 /* lastPeriodIndex= */ windowIndex, 141 /* positionInFirstPeriodUs= */ 0); 142 } 143 144 @Override getPeriodCount()145 public int getPeriodCount() { 146 return ids.length; 147 } 148 149 @Override getPeriod(int periodIndex, Period period, boolean setIds)150 public Period getPeriod(int periodIndex, Period period, boolean setIds) { 151 int id = ids[periodIndex]; 152 return period.set(id, id, periodIndex, durationsUs[periodIndex], 0); 153 } 154 155 @Override getIndexOfPeriod(Object uid)156 public int getIndexOfPeriod(Object uid) { 157 return uid instanceof Integer ? idsToIndex.get((int) uid, C.INDEX_UNSET) : C.INDEX_UNSET; 158 } 159 160 @Override getUidOfPeriod(int periodIndex)161 public Integer getUidOfPeriod(int periodIndex) { 162 return ids[periodIndex]; 163 } 164 165 // equals and hashCode implementations. 166 167 @Override equals(@ullable Object other)168 public boolean equals(@Nullable Object other) { 169 if (this == other) { 170 return true; 171 } else if (!(other instanceof CastTimeline)) { 172 return false; 173 } 174 CastTimeline that = (CastTimeline) other; 175 return Arrays.equals(ids, that.ids) 176 && Arrays.equals(durationsUs, that.durationsUs) 177 && Arrays.equals(defaultPositionsUs, that.defaultPositionsUs) 178 && Arrays.equals(isLive, that.isLive); 179 } 180 181 @Override hashCode()182 public int hashCode() { 183 int result = Arrays.hashCode(ids); 184 result = 31 * result + Arrays.hashCode(durationsUs); 185 result = 31 * result + Arrays.hashCode(defaultPositionsUs); 186 result = 31 * result + Arrays.hashCode(isLive); 187 return result; 188 } 189 190 } 191