Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c9cc147f
authored
Nov 18, 2019
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Revert "Playlist API: add Playlist and PlaylistTest"
This reverts commit
5c2806ec
.
parent
71dd3fe5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
5 additions
and
1222 deletions
library/core/src/main/java/com/google/android/exoplayer2/Playlist.java
library/core/src/main/java/com/google/android/exoplayer2/AbstractConcatenatedTimeline.java → library/core/src/main/java/com/google/android/exoplayer2/source/AbstractConcatenatedTimeline.java
library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java
library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java
library/core/src/main/java/com/google/android/exoplayer2/Playlist.java
deleted
100644 → 0
View file @
71dd3fe5
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
;
import
android.os.Handler
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.analytics.AnalyticsCollector
;
import
com.google.android.exoplayer2.source.MaskingMediaPeriod
;
import
com.google.android.exoplayer2.source.MaskingMediaSource
;
import
com.google.android.exoplayer2.source.MediaPeriod
;
import
com.google.android.exoplayer2.source.MediaSource
;
import
com.google.android.exoplayer2.source.MediaSourceEventListener
;
import
com.google.android.exoplayer2.source.ShuffleOrder
;
import
com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder
;
import
com.google.android.exoplayer2.upstream.Allocator
;
import
com.google.android.exoplayer2.upstream.TransferListener
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.IdentityHashMap
;
import
java.util.Iterator
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
/**
* Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified
* during playback. It is valid for the same {@link MediaSource} instance to be present more than
* once in the playlist.
*
* <p>With the exception of the constructor, all methods are called on the playback thread.
*/
/* package */
class
Playlist
{
/** Listener for source events. */
public
interface
PlaylistInfoRefreshListener
{
/**
* Called when the timeline of a media item has changed and a new timeline that reflects the
* current playlist state needs to be created by calling {@link #createTimeline()}.
*
* <p>Called on the playback thread.
*/
void
onPlaylistUpdateRequested
();
}
private
final
List
<
MediaSourceHolder
>
mediaSourceHolders
;
private
final
Map
<
MediaPeriod
,
MediaSourceHolder
>
mediaSourceByMediaPeriod
;
private
final
Map
<
Object
,
MediaSourceHolder
>
mediaSourceByUid
;
private
final
PlaylistInfoRefreshListener
playlistInfoListener
;
private
final
MediaSourceEventListener
.
EventDispatcher
eventDispatcher
;
private
final
HashMap
<
Playlist
.
MediaSourceHolder
,
MediaSourceAndListener
>
childSources
;
private
final
Set
<
MediaSourceHolder
>
enabledMediaSourceHolders
;
private
ShuffleOrder
shuffleOrder
;
private
boolean
isPrepared
;
@Nullable
private
TransferListener
mediaTransferListener
;
@SuppressWarnings
(
"initialization"
)
public
Playlist
(
PlaylistInfoRefreshListener
listener
)
{
playlistInfoListener
=
listener
;
shuffleOrder
=
new
DefaultShuffleOrder
(
0
);
mediaSourceByMediaPeriod
=
new
IdentityHashMap
<>();
mediaSourceByUid
=
new
HashMap
<>();
mediaSourceHolders
=
new
ArrayList
<>();
eventDispatcher
=
new
MediaSourceEventListener
.
EventDispatcher
();
childSources
=
new
HashMap
<>();
enabledMediaSourceHolders
=
new
HashSet
<>();
}
/**
* Sets the media sources replacing any sources previously contained in the playlist.
*
* @param holders The list of {@link MediaSourceHolder}s to set.
* @param shuffleOrder The new shuffle order.
* @return The new {@link Timeline}.
*/
public
final
Timeline
setMediaSources
(
List
<
MediaSourceHolder
>
holders
,
ShuffleOrder
shuffleOrder
)
{
removeMediaSourcesInternal
(
/* fromIndex= */
0
,
/* toIndex= */
mediaSourceHolders
.
size
());
return
addMediaSources
(
/* index= */
this
.
mediaSourceHolders
.
size
(),
holders
,
shuffleOrder
);
}
/**
* Adds multiple {@link MediaSourceHolder}s to the playlist.
*
* @param index The index at which the new {@link MediaSourceHolder}s will be inserted. This index
* must be in the range of 0 <= index <= {@link #getSize()}.
* @param holders A list of {@link MediaSourceHolder}s to be added.
* @param shuffleOrder The new shuffle order.
* @return The new {@link Timeline}.
*/
public
final
Timeline
addMediaSources
(
int
index
,
List
<
MediaSourceHolder
>
holders
,
ShuffleOrder
shuffleOrder
)
{
if
(!
holders
.
isEmpty
())
{
this
.
shuffleOrder
=
shuffleOrder
;
for
(
int
insertionIndex
=
index
;
insertionIndex
<
index
+
holders
.
size
();
insertionIndex
++)
{
MediaSourceHolder
holder
=
holders
.
get
(
insertionIndex
-
index
);
if
(
insertionIndex
>
0
)
{
MediaSourceHolder
previousHolder
=
mediaSourceHolders
.
get
(
insertionIndex
-
1
);
Timeline
previousTimeline
=
previousHolder
.
mediaSource
.
getTimeline
();
holder
.
reset
(
/* firstWindowInChildIndex= */
previousHolder
.
firstWindowIndexInChild
+
previousTimeline
.
getWindowCount
());
}
else
{
holder
.
reset
(
/* firstWindowIndexInChild= */
0
);
}
Timeline
newTimeline
=
holder
.
mediaSource
.
getTimeline
();
correctOffsets
(
/* startIndex= */
insertionIndex
,
/* windowOffsetUpdate= */
newTimeline
.
getWindowCount
());
mediaSourceHolders
.
add
(
insertionIndex
,
holder
);
mediaSourceByUid
.
put
(
holder
.
uid
,
holder
);
if
(
isPrepared
)
{
prepareChildSource
(
holder
);
if
(
mediaSourceByMediaPeriod
.
isEmpty
())
{
enabledMediaSourceHolders
.
add
(
holder
);
}
else
{
disableChildSource
(
holder
);
}
}
}
}
return
createTimeline
();
}
/**
* Removes a range of {@link MediaSourceHolder}s from the playlist, by specifying an initial index
* (included) and a final index (excluded).
*
* <p>Note: when specified range is empty, no actual media source is removed and no exception is
* thrown.
*
* @param fromIndex The initial range index, pointing to the first media source that will be
* removed. This index must be in the range of 0 <= index <= {@link #getSize()}.
* @param toIndex The final range index, pointing to the first media source that will be left
* untouched. This index must be in the range of 0 <= index <= {@link #getSize()}.
* @param shuffleOrder The new shuffle order.
* @return The new {@link Timeline}.
* @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0,
* {@code toIndex} > {@link #getSize()}, {@code fromIndex} > {@code toIndex}
*/
public
final
Timeline
removeMediaSourceRange
(
int
fromIndex
,
int
toIndex
,
ShuffleOrder
shuffleOrder
)
{
Assertions
.
checkArgument
(
fromIndex
>=
0
&&
fromIndex
<=
toIndex
&&
toIndex
<=
getSize
());
this
.
shuffleOrder
=
shuffleOrder
;
removeMediaSourcesInternal
(
fromIndex
,
toIndex
);
return
createTimeline
();
}
/**
* Moves an existing media source within the playlist.
*
* @param currentIndex The current index of the media source in the playlist. This index must be
* in the range of 0 <= index < {@link #getSize()}.
* @param newIndex The target index of the media source in the playlist. This index must be in the
* range of 0 <= index < {@link #getSize()}.
* @param shuffleOrder The new shuffle order.
* @return The new {@link Timeline}.
* @throws IllegalArgumentException When an index is invalid, i.e. {@code currentIndex} < 0,
* {@code currentIndex} >= {@link #getSize()}, {@code newIndex} < 0
*/
public
final
Timeline
moveMediaSource
(
int
currentIndex
,
int
newIndex
,
ShuffleOrder
shuffleOrder
)
{
return
moveMediaSourceRange
(
currentIndex
,
currentIndex
+
1
,
newIndex
,
shuffleOrder
);
}
/**
* Moves a range of media sources within the playlist.
*
* <p>Note: when specified range is empty or the from index equals the new from index, no actual
* media source is moved and no exception is thrown.
*
* @param fromIndex The initial range index, pointing to the first media source of the range that
* will be moved. This index must be in the range of 0 <= index <= {@link #getSize()}.
* @param toIndex The final range index, pointing to the first media source that will be left
* untouched. This index must be larger or equals than {@code fromIndex}.
* @param newFromIndex The target index of the first media source of the range that will be moved.
* @param shuffleOrder The new shuffle order.
* @return The new {@link Timeline}.
* @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} < 0,
* {@code toIndex} < {@code fromIndex}, {@code fromIndex} > {@code toIndex}, {@code
* newFromIndex} < 0
*/
public
Timeline
moveMediaSourceRange
(
int
fromIndex
,
int
toIndex
,
int
newFromIndex
,
ShuffleOrder
shuffleOrder
)
{
Assertions
.
checkArgument
(
fromIndex
>=
0
&&
fromIndex
<=
toIndex
&&
toIndex
<=
getSize
()
&&
newFromIndex
>=
0
);
this
.
shuffleOrder
=
shuffleOrder
;
if
(
fromIndex
==
toIndex
||
fromIndex
==
newFromIndex
)
{
return
createTimeline
();
}
int
startIndex
=
Math
.
min
(
fromIndex
,
newFromIndex
);
int
newEndIndex
=
newFromIndex
+
(
toIndex
-
fromIndex
)
-
1
;
int
endIndex
=
Math
.
max
(
newEndIndex
,
toIndex
-
1
);
int
windowOffset
=
mediaSourceHolders
.
get
(
startIndex
).
firstWindowIndexInChild
;
moveMediaSourceHolders
(
mediaSourceHolders
,
fromIndex
,
toIndex
,
newFromIndex
);
for
(
int
i
=
startIndex
;
i
<=
endIndex
;
i
++)
{
MediaSourceHolder
holder
=
mediaSourceHolders
.
get
(
i
);
holder
.
firstWindowIndexInChild
=
windowOffset
;
windowOffset
+=
holder
.
mediaSource
.
getTimeline
().
getWindowCount
();
}
return
createTimeline
();
}
/** Clears the playlist. */
public
final
Timeline
clear
(
@Nullable
ShuffleOrder
shuffleOrder
)
{
this
.
shuffleOrder
=
shuffleOrder
!=
null
?
shuffleOrder
:
this
.
shuffleOrder
.
cloneAndClear
();
removeMediaSourcesInternal
(
/* fromIndex= */
0
,
/* toIndex= */
getSize
());
return
createTimeline
();
}
/** Whether the playlist is prepared. */
public
final
boolean
isPrepared
()
{
return
isPrepared
;
}
/** Returns the number of media sources in the playlist. */
public
final
int
getSize
()
{
return
mediaSourceHolders
.
size
();
}
/**
* Sets the {@link AnalyticsCollector}.
*
* @param handler The handler on which to call the collector.
* @param analyticsCollector The analytics collector.
*/
public
final
void
setAnalyticsCollector
(
Handler
handler
,
AnalyticsCollector
analyticsCollector
)
{
eventDispatcher
.
addEventListener
(
handler
,
analyticsCollector
);
}
/**
* Sets a new shuffle order to use when shuffling the child media sources.
*
* @param shuffleOrder A {@link ShuffleOrder}.
*/
public
final
Timeline
setShuffleOrder
(
ShuffleOrder
shuffleOrder
)
{
int
size
=
getSize
();
if
(
shuffleOrder
.
getLength
()
!=
size
)
{
shuffleOrder
=
shuffleOrder
.
cloneAndClear
()
.
cloneAndInsert
(
/* insertionIndex= */
0
,
/* insertionCount= */
size
);
}
this
.
shuffleOrder
=
shuffleOrder
;
return
createTimeline
();
}
/** Prepares the playlist. */
public
final
void
prepare
(
@Nullable
TransferListener
mediaTransferListener
)
{
Assertions
.
checkState
(!
isPrepared
);
this
.
mediaTransferListener
=
mediaTransferListener
;
for
(
int
i
=
0
;
i
<
mediaSourceHolders
.
size
();
i
++)
{
MediaSourceHolder
mediaSourceHolder
=
mediaSourceHolders
.
get
(
i
);
prepareChildSource
(
mediaSourceHolder
);
enabledMediaSourceHolders
.
add
(
mediaSourceHolder
);
}
isPrepared
=
true
;
}
/**
* Returns a new {@link MediaPeriod} identified by {@code periodId}.
*
* @param id The identifier of the period.
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
* @param startPositionUs The expected start position, in microseconds.
* @return A new {@link MediaPeriod}.
*/
public
MediaPeriod
createPeriod
(
MediaSource
.
MediaPeriodId
id
,
Allocator
allocator
,
long
startPositionUs
)
{
Object
mediaSourceHolderUid
=
getMediaSourceHolderUid
(
id
.
periodUid
);
MediaSource
.
MediaPeriodId
childMediaPeriodId
=
id
.
copyWithPeriodUid
(
getChildPeriodUid
(
id
.
periodUid
));
MediaSourceHolder
holder
=
Assertions
.
checkNotNull
(
mediaSourceByUid
.
get
(
mediaSourceHolderUid
));
enableMediaSource
(
holder
);
holder
.
activeMediaPeriodIds
.
add
(
childMediaPeriodId
);
MediaPeriod
mediaPeriod
=
holder
.
mediaSource
.
createPeriod
(
childMediaPeriodId
,
allocator
,
startPositionUs
);
mediaSourceByMediaPeriod
.
put
(
mediaPeriod
,
holder
);
disableUnusedMediaSources
();
return
mediaPeriod
;
}
/**
* Releases the period.
*
* @param mediaPeriod The period to release.
*/
public
final
void
releasePeriod
(
MediaPeriod
mediaPeriod
)
{
MediaSourceHolder
holder
=
Assertions
.
checkNotNull
(
mediaSourceByMediaPeriod
.
remove
(
mediaPeriod
));
holder
.
mediaSource
.
releasePeriod
(
mediaPeriod
);
holder
.
activeMediaPeriodIds
.
remove
(((
MaskingMediaPeriod
)
mediaPeriod
).
id
);
if
(!
mediaSourceByMediaPeriod
.
isEmpty
())
{
disableUnusedMediaSources
();
}
maybeReleaseChildSource
(
holder
);
}
/** Releases the playlist. */
public
final
void
release
()
{
for
(
MediaSourceAndListener
childSource
:
childSources
.
values
())
{
childSource
.
mediaSource
.
releaseSource
(
childSource
.
caller
);
childSource
.
mediaSource
.
removeEventListener
(
childSource
.
eventListener
);
}
childSources
.
clear
();
enabledMediaSourceHolders
.
clear
();
isPrepared
=
false
;
}
/** Throws any pending error encountered while loading or refreshing. */
public
final
void
maybeThrowSourceInfoRefreshError
()
throws
IOException
{
for
(
MediaSourceAndListener
childSource
:
childSources
.
values
())
{
childSource
.
mediaSource
.
maybeThrowSourceInfoRefreshError
();
}
}
/** Creates a timeline reflecting the current state of the playlist. */
public
final
Timeline
createTimeline
()
{
if
(
mediaSourceHolders
.
isEmpty
())
{
return
Timeline
.
EMPTY
;
}
int
windowOffset
=
0
;
for
(
int
i
=
0
;
i
<
mediaSourceHolders
.
size
();
i
++)
{
MediaSourceHolder
mediaSourceHolder
=
mediaSourceHolders
.
get
(
i
);
mediaSourceHolder
.
firstWindowIndexInChild
=
windowOffset
;
windowOffset
+=
mediaSourceHolder
.
mediaSource
.
getTimeline
().
getWindowCount
();
}
return
new
PlaylistTimeline
(
mediaSourceHolders
,
shuffleOrder
);
}
// Internal methods.
private
void
enableMediaSource
(
MediaSourceHolder
mediaSourceHolder
)
{
enabledMediaSourceHolders
.
add
(
mediaSourceHolder
);
@Nullable
MediaSourceAndListener
enabledChild
=
childSources
.
get
(
mediaSourceHolder
);
if
(
enabledChild
!=
null
)
{
enabledChild
.
mediaSource
.
enable
(
enabledChild
.
caller
);
}
}
private
void
disableUnusedMediaSources
()
{
Iterator
<
MediaSourceHolder
>
iterator
=
enabledMediaSourceHolders
.
iterator
();
while
(
iterator
.
hasNext
())
{
MediaSourceHolder
holder
=
iterator
.
next
();
if
(
holder
.
activeMediaPeriodIds
.
isEmpty
())
{
disableChildSource
(
holder
);
iterator
.
remove
();
}
}
}
private
void
disableChildSource
(
MediaSourceHolder
holder
)
{
@Nullable
MediaSourceAndListener
disabledChild
=
childSources
.
get
(
holder
);
if
(
disabledChild
!=
null
)
{
disabledChild
.
mediaSource
.
disable
(
disabledChild
.
caller
);
}
}
private
void
removeMediaSourcesInternal
(
int
fromIndex
,
int
toIndex
)
{
for
(
int
index
=
toIndex
-
1
;
index
>=
fromIndex
;
index
--)
{
MediaSourceHolder
holder
=
mediaSourceHolders
.
remove
(
index
);
mediaSourceByUid
.
remove
(
holder
.
uid
);
Timeline
oldTimeline
=
holder
.
mediaSource
.
getTimeline
();
correctOffsets
(
/* startIndex= */
index
,
/* windowOffsetUpdate= */
-
oldTimeline
.
getWindowCount
());
holder
.
isRemoved
=
true
;
if
(
isPrepared
)
{
maybeReleaseChildSource
(
holder
);
}
}
}
private
void
correctOffsets
(
int
startIndex
,
int
windowOffsetUpdate
)
{
for
(
int
i
=
startIndex
;
i
<
mediaSourceHolders
.
size
();
i
++)
{
MediaSourceHolder
mediaSourceHolder
=
mediaSourceHolders
.
get
(
i
);
mediaSourceHolder
.
firstWindowIndexInChild
+=
windowOffsetUpdate
;
}
}
// Internal methods to manage child sources.
@Nullable
private
static
MediaSource
.
MediaPeriodId
getMediaPeriodIdForChildMediaPeriodId
(
MediaSourceHolder
mediaSourceHolder
,
MediaSource
.
MediaPeriodId
mediaPeriodId
)
{
for
(
int
i
=
0
;
i
<
mediaSourceHolder
.
activeMediaPeriodIds
.
size
();
i
++)
{
// Ensure the reported media period id has the same window sequence number as the one created
// by this media source. Otherwise it does not belong to this child source.
if
(
mediaSourceHolder
.
activeMediaPeriodIds
.
get
(
i
).
windowSequenceNumber
==
mediaPeriodId
.
windowSequenceNumber
)
{
Object
periodUid
=
getPeriodUid
(
mediaSourceHolder
,
mediaPeriodId
.
periodUid
);
return
mediaPeriodId
.
copyWithPeriodUid
(
periodUid
);
}
}
return
null
;
}
private
static
int
getWindowIndexForChildWindowIndex
(
MediaSourceHolder
mediaSourceHolder
,
int
windowIndex
)
{
return
windowIndex
+
mediaSourceHolder
.
firstWindowIndexInChild
;
}
private
void
prepareChildSource
(
MediaSourceHolder
holder
)
{
MediaSource
mediaSource
=
holder
.
mediaSource
;
MediaSource
.
MediaSourceCaller
caller
=
(
source
,
timeline
)
->
playlistInfoListener
.
onPlaylistUpdateRequested
();
MediaSourceEventListener
eventListener
=
new
ForwardingEventListener
(
holder
);
childSources
.
put
(
holder
,
new
MediaSourceAndListener
(
mediaSource
,
caller
,
eventListener
));
mediaSource
.
addEventListener
(
new
Handler
(),
eventListener
);
mediaSource
.
prepareSource
(
caller
,
mediaTransferListener
);
}
private
void
maybeReleaseChildSource
(
MediaSourceHolder
mediaSourceHolder
)
{
// Release if the source has been removed from the playlist and no periods are still active.
if
(
mediaSourceHolder
.
isRemoved
&&
mediaSourceHolder
.
activeMediaPeriodIds
.
isEmpty
())
{
MediaSourceAndListener
removedChild
=
Assertions
.
checkNotNull
(
childSources
.
remove
(
mediaSourceHolder
));
removedChild
.
mediaSource
.
releaseSource
(
removedChild
.
caller
);
removedChild
.
mediaSource
.
removeEventListener
(
removedChild
.
eventListener
);
enabledMediaSourceHolders
.
remove
(
mediaSourceHolder
);
}
}
/** Return uid of media source holder from period uid of concatenated source. */
private
static
Object
getMediaSourceHolderUid
(
Object
periodUid
)
{
return
PlaylistTimeline
.
getChildTimelineUidFromConcatenatedUid
(
periodUid
);
}
/** Return uid of child period from period uid of concatenated source. */
private
static
Object
getChildPeriodUid
(
Object
periodUid
)
{
return
PlaylistTimeline
.
getChildPeriodUidFromConcatenatedUid
(
periodUid
);
}
private
static
Object
getPeriodUid
(
MediaSourceHolder
holder
,
Object
childPeriodUid
)
{
return
PlaylistTimeline
.
getConcatenatedUid
(
holder
.
uid
,
childPeriodUid
);
}
/* package */
static
void
moveMediaSourceHolders
(
List
<
MediaSourceHolder
>
mediaSourceHolders
,
int
fromIndex
,
int
toIndex
,
int
newFromIndex
)
{
MediaSourceHolder
[]
removedItems
=
new
MediaSourceHolder
[
toIndex
-
fromIndex
];
for
(
int
i
=
removedItems
.
length
-
1
;
i
>=
0
;
i
--)
{
removedItems
[
i
]
=
mediaSourceHolders
.
remove
(
fromIndex
+
i
);
}
mediaSourceHolders
.
addAll
(
Math
.
min
(
newFromIndex
,
mediaSourceHolders
.
size
()),
Arrays
.
asList
(
removedItems
));
}
/** Data class to hold playlist media sources together with meta data needed to process them. */
/* package */
static
final
class
MediaSourceHolder
{
public
final
MaskingMediaSource
mediaSource
;
public
final
Object
uid
;
public
final
List
<
MediaSource
.
MediaPeriodId
>
activeMediaPeriodIds
;
public
int
firstWindowIndexInChild
;
public
boolean
isRemoved
;
public
MediaSourceHolder
(
MediaSource
mediaSource
,
boolean
useLazyPreparation
)
{
this
.
mediaSource
=
new
MaskingMediaSource
(
mediaSource
,
useLazyPreparation
);
this
.
activeMediaPeriodIds
=
new
ArrayList
<>();
this
.
uid
=
new
Object
();
}
public
void
reset
(
int
firstWindowIndexInChild
)
{
this
.
firstWindowIndexInChild
=
firstWindowIndexInChild
;
this
.
isRemoved
=
false
;
this
.
activeMediaPeriodIds
.
clear
();
}
}
/** Timeline exposing concatenated timelines of playlist media sources. */
/* package */
static
final
class
PlaylistTimeline
extends
AbstractConcatenatedTimeline
{
private
final
int
windowCount
;
private
final
int
periodCount
;
private
final
int
[]
firstPeriodInChildIndices
;
private
final
int
[]
firstWindowInChildIndices
;
private
final
Timeline
[]
timelines
;
private
final
Object
[]
uids
;
private
final
HashMap
<
Object
,
Integer
>
childIndexByUid
;
public
PlaylistTimeline
(
Collection
<
MediaSourceHolder
>
mediaSourceHolders
,
ShuffleOrder
shuffleOrder
)
{
super
(
/* isAtomic= */
false
,
shuffleOrder
);
int
childCount
=
mediaSourceHolders
.
size
();
firstPeriodInChildIndices
=
new
int
[
childCount
];
firstWindowInChildIndices
=
new
int
[
childCount
];
timelines
=
new
Timeline
[
childCount
];
uids
=
new
Object
[
childCount
];
childIndexByUid
=
new
HashMap
<>();
int
index
=
0
;
int
windowCount
=
0
;
int
periodCount
=
0
;
for
(
MediaSourceHolder
mediaSourceHolder
:
mediaSourceHolders
)
{
timelines
[
index
]
=
mediaSourceHolder
.
mediaSource
.
getTimeline
();
firstWindowInChildIndices
[
index
]
=
windowCount
;
firstPeriodInChildIndices
[
index
]
=
periodCount
;
windowCount
+=
timelines
[
index
].
getWindowCount
();
periodCount
+=
timelines
[
index
].
getPeriodCount
();
uids
[
index
]
=
mediaSourceHolder
.
uid
;
childIndexByUid
.
put
(
uids
[
index
],
index
++);
}
this
.
windowCount
=
windowCount
;
this
.
periodCount
=
periodCount
;
}
@Override
protected
int
getChildIndexByPeriodIndex
(
int
periodIndex
)
{
return
Util
.
binarySearchFloor
(
firstPeriodInChildIndices
,
periodIndex
+
1
,
false
,
false
);
}
@Override
protected
int
getChildIndexByWindowIndex
(
int
windowIndex
)
{
return
Util
.
binarySearchFloor
(
firstWindowInChildIndices
,
windowIndex
+
1
,
false
,
false
);
}
@Override
protected
int
getChildIndexByChildUid
(
Object
childUid
)
{
Integer
index
=
childIndexByUid
.
get
(
childUid
);
return
index
==
null
?
C
.
INDEX_UNSET
:
index
;
}
@Override
protected
Timeline
getTimelineByChildIndex
(
int
childIndex
)
{
return
timelines
[
childIndex
];
}
@Override
protected
int
getFirstPeriodIndexByChildIndex
(
int
childIndex
)
{
return
firstPeriodInChildIndices
[
childIndex
];
}
@Override
protected
int
getFirstWindowIndexByChildIndex
(
int
childIndex
)
{
return
firstWindowInChildIndices
[
childIndex
];
}
@Override
protected
Object
getChildUidByChildIndex
(
int
childIndex
)
{
return
uids
[
childIndex
];
}
@Override
public
int
getWindowCount
()
{
return
windowCount
;
}
@Override
public
int
getPeriodCount
()
{
return
periodCount
;
}
}
private
static
final
class
MediaSourceAndListener
{
public
final
MediaSource
mediaSource
;
public
final
MediaSource
.
MediaSourceCaller
caller
;
public
final
MediaSourceEventListener
eventListener
;
public
MediaSourceAndListener
(
MediaSource
mediaSource
,
MediaSource
.
MediaSourceCaller
caller
,
MediaSourceEventListener
eventListener
)
{
this
.
mediaSource
=
mediaSource
;
this
.
caller
=
caller
;
this
.
eventListener
=
eventListener
;
}
}
private
final
class
ForwardingEventListener
implements
MediaSourceEventListener
{
private
final
Playlist
.
MediaSourceHolder
id
;
private
EventDispatcher
eventDispatcher
;
public
ForwardingEventListener
(
Playlist
.
MediaSourceHolder
id
)
{
eventDispatcher
=
Playlist
.
this
.
eventDispatcher
;
this
.
id
=
id
;
}
@Override
public
void
onMediaPeriodCreated
(
int
windowIndex
,
MediaSource
.
MediaPeriodId
mediaPeriodId
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
mediaPeriodCreated
();
}
}
@Override
public
void
onMediaPeriodReleased
(
int
windowIndex
,
MediaSource
.
MediaPeriodId
mediaPeriodId
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
mediaPeriodReleased
();
}
}
@Override
public
void
onLoadStarted
(
int
windowIndex
,
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
,
LoadEventInfo
loadEventData
,
MediaLoadData
mediaLoadData
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
loadStarted
(
loadEventData
,
mediaLoadData
);
}
}
@Override
public
void
onLoadCompleted
(
int
windowIndex
,
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
,
LoadEventInfo
loadEventData
,
MediaLoadData
mediaLoadData
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
loadCompleted
(
loadEventData
,
mediaLoadData
);
}
}
@Override
public
void
onLoadCanceled
(
int
windowIndex
,
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
,
LoadEventInfo
loadEventData
,
MediaLoadData
mediaLoadData
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
loadCanceled
(
loadEventData
,
mediaLoadData
);
}
}
@Override
public
void
onLoadError
(
int
windowIndex
,
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
,
LoadEventInfo
loadEventData
,
MediaLoadData
mediaLoadData
,
IOException
error
,
boolean
wasCanceled
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
loadError
(
loadEventData
,
mediaLoadData
,
error
,
wasCanceled
);
}
}
@Override
public
void
onReadingStarted
(
int
windowIndex
,
MediaSource
.
MediaPeriodId
mediaPeriodId
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
readingStarted
();
}
}
@Override
public
void
onUpstreamDiscarded
(
int
windowIndex
,
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
,
MediaLoadData
mediaLoadData
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
upstreamDiscarded
(
mediaLoadData
);
}
}
@Override
public
void
onDownstreamFormatChanged
(
int
windowIndex
,
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
,
MediaLoadData
mediaLoadData
)
{
if
(
maybeUpdateEventDispatcher
(
windowIndex
,
mediaPeriodId
))
{
eventDispatcher
.
downstreamFormatChanged
(
mediaLoadData
);
}
}
/** Updates the event dispatcher and returns whether the event should be dispatched. */
private
boolean
maybeUpdateEventDispatcher
(
int
childWindowIndex
,
@Nullable
MediaSource
.
MediaPeriodId
childMediaPeriodId
)
{
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
=
null
;
if
(
childMediaPeriodId
!=
null
)
{
mediaPeriodId
=
getMediaPeriodIdForChildMediaPeriodId
(
id
,
childMediaPeriodId
);
if
(
mediaPeriodId
==
null
)
{
// Media period not found. Ignore event.
return
false
;
}
}
int
windowIndex
=
getWindowIndexForChildWindowIndex
(
id
,
childWindowIndex
);
if
(
eventDispatcher
.
windowIndex
!=
windowIndex
||
!
Util
.
areEqual
(
eventDispatcher
.
mediaPeriodId
,
mediaPeriodId
))
{
eventDispatcher
=
Playlist
.
this
.
eventDispatcher
.
withParameters
(
windowIndex
,
mediaPeriodId
,
/* mediaTimeOffsetMs= */
0L
);
}
return
true
;
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/AbstractConcatenatedTimeline.java
→
library/core/src/main/java/com/google/android/exoplayer2/
source/
AbstractConcatenatedTimeline.java
View file @
c9cc147f
...
...
@@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
;
package
com
.
google
.
android
.
exoplayer2
.
source
;
import
android.util.Pair
;
import
com.google.android.exoplayer2.source.ShuffleOrder
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Player
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.util.Assertions
;
/** Abstract base class for the concatenation of one or more {@link Timeline}s. */
public
abstract
class
AbstractConcatenatedTimeline
extends
Timeline
{
/* package */
abstract
class
AbstractConcatenatedTimeline
extends
Timeline
{
private
final
int
childCount
;
private
final
ShuffleOrder
shuffleOrder
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/ConcatenatingMediaSource.java
View file @
c9cc147f
...
...
@@ -19,7 +19,6 @@ import android.os.Handler;
import
android.os.Message
;
import
androidx.annotation.GuardedBy
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.AbstractConcatenatedTimeline
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.source.ConcatenatingMediaSource.MediaSourceHolder
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/LoopingMediaSource.java
View file @
c9cc147f
...
...
@@ -16,7 +16,6 @@
package
com
.
google
.
android
.
exoplayer2
.
source
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.AbstractConcatenatedTimeline
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlayer
;
import
com.google.android.exoplayer2.Player
;
...
...
library/core/src/test/java/com/google/android/exoplayer2/PlaylistTest.java
deleted
100644 → 0
View file @
71dd3fe5
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
assertNotSame
;
import
static
org
.
junit
.
Assert
.
assertSame
;
import
static
org
.
mockito
.
ArgumentMatchers
.
any
;
import
static
org
.
mockito
.
ArgumentMatchers
.
isNull
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
times
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.source.MediaSource
;
import
com.google.android.exoplayer2.source.ShuffleOrder
;
import
com.google.android.exoplayer2.testutil.FakeMediaSource
;
import
com.google.android.exoplayer2.testutil.FakeShuffleOrder
;
import
com.google.android.exoplayer2.testutil.FakeTimeline
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit test for {@link Playlist}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
PlaylistTest
{
private
static
final
int
PLAYLIST_SIZE
=
4
;
private
Playlist
playlist
;
@Before
public
void
setUp
()
{
playlist
=
new
Playlist
(
mock
(
Playlist
.
PlaylistInfoRefreshListener
.
class
));
}
@Test
public
void
testEmptyPlaylist_expectConstantTimelineInstanceEMPTY
()
{
ShuffleOrder
.
DefaultShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
0
);
List
<
Playlist
.
MediaSourceHolder
>
fakeHolders
=
createFakeHolders
();
Timeline
timeline
=
playlist
.
setMediaSources
(
fakeHolders
,
shuffleOrder
);
assertNotSame
(
timeline
,
Timeline
.
EMPTY
);
// Remove all media sources.
timeline
=
playlist
.
removeMediaSourceRange
(
/* fromIndex= */
0
,
/* toIndex= */
timeline
.
getWindowCount
(),
shuffleOrder
);
assertSame
(
timeline
,
Timeline
.
EMPTY
);
timeline
=
playlist
.
setMediaSources
(
fakeHolders
,
shuffleOrder
);
assertNotSame
(
timeline
,
Timeline
.
EMPTY
);
// Clear.
timeline
=
playlist
.
clear
(
shuffleOrder
);
assertSame
(
timeline
,
Timeline
.
EMPTY
);
}
@Test
public
void
testPrepareAndReprepareAfterRelease_expectSourcePreparationAfterPlaylistPrepare
()
{
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
playlist
.
setMediaSources
(
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
),
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
2
));
// Verify prepare is called once on prepare.
verify
(
mockMediaSource1
,
times
(
0
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
verify
(
mockMediaSource2
,
times
(
0
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
playlist
.
prepare
(
/* mediaTransferListener= */
null
);
assertThat
(
playlist
.
isPrepared
()).
isTrue
();
// Verify prepare is called once on prepare.
verify
(
mockMediaSource1
,
times
(
1
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
verify
(
mockMediaSource2
,
times
(
1
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
playlist
.
release
();
playlist
.
prepare
(
/* mediaTransferListener= */
null
);
// Verify prepare is called a second time on re-prepare.
verify
(
mockMediaSource1
,
times
(
2
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
verify
(
mockMediaSource2
,
times
(
2
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
}
@Test
public
void
testSetMediaSources_playlistUnprepared_notUsingLazyPreparation
()
{
ShuffleOrder
.
DefaultShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
2
);
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
List
<
Playlist
.
MediaSourceHolder
>
mediaSources
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
);
Timeline
timeline
=
playlist
.
setMediaSources
(
mediaSources
,
shuffleOrder
);
assertThat
(
timeline
.
getWindowCount
()).
isEqualTo
(
2
);
assertThat
(
playlist
.
getSize
()).
isEqualTo
(
2
);
// Assert holder offsets have been set properly
for
(
int
i
=
0
;
i
<
mediaSources
.
size
();
i
++)
{
Playlist
.
MediaSourceHolder
mediaSourceHolder
=
mediaSources
.
get
(
i
);
assertThat
(
mediaSourceHolder
.
isRemoved
).
isFalse
();
assertThat
(
mediaSourceHolder
.
firstWindowIndexInChild
).
isEqualTo
(
i
);
}
// Set media items again. The second holder is re-used.
List
<
Playlist
.
MediaSourceHolder
>
moreMediaSources
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mock
(
MediaSource
.
class
));
moreMediaSources
.
add
(
mediaSources
.
get
(
1
));
timeline
=
playlist
.
setMediaSources
(
moreMediaSources
,
shuffleOrder
);
assertThat
(
playlist
.
getSize
()).
isEqualTo
(
2
);
assertThat
(
timeline
.
getWindowCount
()).
isEqualTo
(
2
);
for
(
int
i
=
0
;
i
<
moreMediaSources
.
size
();
i
++)
{
Playlist
.
MediaSourceHolder
mediaSourceHolder
=
moreMediaSources
.
get
(
i
);
assertThat
(
mediaSourceHolder
.
isRemoved
).
isFalse
();
assertThat
(
mediaSourceHolder
.
firstWindowIndexInChild
).
isEqualTo
(
i
);
}
// Expect removed holders and sources to be removed without releasing.
verify
(
mockMediaSource1
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
assertThat
(
mediaSources
.
get
(
0
).
isRemoved
).
isTrue
();
// Expect re-used holder and source not to be removed.
verify
(
mockMediaSource2
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
assertThat
(
mediaSources
.
get
(
1
).
isRemoved
).
isFalse
();
}
@Test
public
void
testSetMediaSources_playlistPrepared_notUsingLazyPreparation
()
{
ShuffleOrder
.
DefaultShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
2
);
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
List
<
Playlist
.
MediaSourceHolder
>
mediaSources
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
);
playlist
.
prepare
(
/* mediaTransferListener= */
null
);
playlist
.
setMediaSources
(
mediaSources
,
shuffleOrder
);
// Verify sources are prepared.
verify
(
mockMediaSource1
,
times
(
1
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
verify
(
mockMediaSource2
,
times
(
1
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
// Set media items again. The second holder is re-used.
List
<
Playlist
.
MediaSourceHolder
>
moreMediaSources
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mock
(
MediaSource
.
class
));
moreMediaSources
.
add
(
mediaSources
.
get
(
1
));
playlist
.
setMediaSources
(
moreMediaSources
,
shuffleOrder
);
// Expect removed holders and sources to be removed and released.
verify
(
mockMediaSource1
,
times
(
1
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
assertThat
(
mediaSources
.
get
(
0
).
isRemoved
).
isTrue
();
// Expect re-used holder and source not to be removed but released.
verify
(
mockMediaSource2
,
times
(
1
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
assertThat
(
mediaSources
.
get
(
1
).
isRemoved
).
isFalse
();
verify
(
mockMediaSource2
,
times
(
2
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
}
@Test
public
void
testAddMediaSources_playlistUnprepared_notUsingLazyPreparation_expectUnprepared
()
{
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
List
<
Playlist
.
MediaSourceHolder
>
mediaSources
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
);
playlist
.
addMediaSources
(
/* index= */
0
,
mediaSources
,
new
ShuffleOrder
.
DefaultShuffleOrder
(
2
));
assertThat
(
playlist
.
getSize
()).
isEqualTo
(
2
);
// Verify lazy initialization does not call prepare on sources.
verify
(
mockMediaSource1
,
times
(
0
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
verify
(
mockMediaSource2
,
times
(
0
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
for
(
int
i
=
0
;
i
<
mediaSources
.
size
();
i
++)
{
assertThat
(
mediaSources
.
get
(
i
).
firstWindowIndexInChild
).
isEqualTo
(
i
);
assertThat
(
mediaSources
.
get
(
i
).
isRemoved
).
isFalse
();
}
// Add for more sources in between.
List
<
Playlist
.
MediaSourceHolder
>
moreMediaSources
=
createFakeHolders
();
playlist
.
addMediaSources
(
/* index= */
1
,
moreMediaSources
,
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
3
));
assertThat
(
mediaSources
.
get
(
0
).
firstWindowIndexInChild
).
isEqualTo
(
0
);
assertThat
(
moreMediaSources
.
get
(
0
).
firstWindowIndexInChild
).
isEqualTo
(
1
);
assertThat
(
moreMediaSources
.
get
(
3
).
firstWindowIndexInChild
).
isEqualTo
(
4
);
assertThat
(
mediaSources
.
get
(
1
).
firstWindowIndexInChild
).
isEqualTo
(
5
);
}
@Test
public
void
testAddMediaSources_playlistPrepared_notUsingLazyPreparation_expectPrepared
()
{
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
playlist
.
prepare
(
/* mediaTransferListener= */
null
);
playlist
.
addMediaSources
(
/* index= */
0
,
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
),
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
2
));
// Verify prepare is called on sources when added.
verify
(
mockMediaSource1
,
times
(
1
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
verify
(
mockMediaSource2
,
times
(
1
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
}
@Test
public
void
testMoveMediaSources
()
{
ShuffleOrder
.
DefaultShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
4
);
List
<
Playlist
.
MediaSourceHolder
>
holders
=
createFakeHolders
();
playlist
.
addMediaSources
(
/* index= */
0
,
holders
,
shuffleOrder
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
playlist
.
moveMediaSource
(
/* currentIndex= */
0
,
/* newIndex= */
3
,
shuffleOrder
);
assertFirstWindowInChildIndices
(
holders
,
3
,
0
,
1
,
2
);
playlist
.
moveMediaSource
(
/* currentIndex= */
3
,
/* newIndex= */
0
,
shuffleOrder
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
0
,
/* toIndex= */
2
,
/* newFromIndex= */
2
,
shuffleOrder
);
assertFirstWindowInChildIndices
(
holders
,
2
,
3
,
0
,
1
);
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
2
,
/* toIndex= */
4
,
/* newFromIndex= */
0
,
shuffleOrder
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
0
,
/* toIndex= */
2
,
/* newFromIndex= */
2
,
shuffleOrder
);
assertFirstWindowInChildIndices
(
holders
,
2
,
3
,
0
,
1
);
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
2
,
/* toIndex= */
3
,
/* newFromIndex= */
0
,
shuffleOrder
);
assertFirstWindowInChildIndices
(
holders
,
0
,
3
,
1
,
2
);
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
3
,
/* toIndex= */
4
,
/* newFromIndex= */
1
,
shuffleOrder
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
// No-ops.
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
0
,
/* toIndex= */
4
,
/* newFromIndex= */
0
,
shuffleOrder
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
0
,
/* toIndex= */
0
,
/* newFromIndex= */
3
,
shuffleOrder
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
}
@Test
public
void
testRemoveMediaSources_whenUnprepared_expectNoRelease
()
{
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource3
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource4
=
mock
(
MediaSource
.
class
);
ShuffleOrder
.
DefaultShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
4
);
List
<
Playlist
.
MediaSourceHolder
>
holders
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
,
mockMediaSource3
,
mockMediaSource4
);
playlist
.
addMediaSources
(
/* index= */
0
,
holders
,
shuffleOrder
);
playlist
.
removeMediaSourceRange
(
/* fromIndex= */
1
,
/* toIndex= */
3
,
shuffleOrder
);
assertThat
(
playlist
.
getSize
()).
isEqualTo
(
2
);
Playlist
.
MediaSourceHolder
removedHolder1
=
holders
.
remove
(
1
);
Playlist
.
MediaSourceHolder
removedHolder2
=
holders
.
remove
(
1
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
assertThat
(
removedHolder1
.
isRemoved
).
isTrue
();
assertThat
(
removedHolder2
.
isRemoved
).
isTrue
();
verify
(
mockMediaSource1
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
verify
(
mockMediaSource2
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
verify
(
mockMediaSource3
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
verify
(
mockMediaSource4
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
}
@Test
public
void
testRemoveMediaSources_whenPrepared_expectRelease
()
{
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource3
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource4
=
mock
(
MediaSource
.
class
);
ShuffleOrder
.
DefaultShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
4
);
List
<
Playlist
.
MediaSourceHolder
>
holders
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
,
mockMediaSource3
,
mockMediaSource4
);
playlist
.
prepare
(
/* mediaTransferListener */
null
);
playlist
.
addMediaSources
(
/* index= */
0
,
holders
,
shuffleOrder
);
playlist
.
removeMediaSourceRange
(
/* fromIndex= */
1
,
/* toIndex= */
3
,
shuffleOrder
);
assertThat
(
playlist
.
getSize
()).
isEqualTo
(
2
);
holders
.
remove
(
2
);
holders
.
remove
(
1
);
assertDefaultFirstWindowInChildIndexOrder
(
holders
);
verify
(
mockMediaSource1
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
verify
(
mockMediaSource2
,
times
(
1
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
verify
(
mockMediaSource3
,
times
(
1
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
verify
(
mockMediaSource4
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
}
@Test
public
void
testRelease_playlistUnprepared_expectSourcesNotReleased
()
{
MediaSource
mockMediaSource
=
mock
(
MediaSource
.
class
);
Playlist
.
MediaSourceHolder
mediaSourceHolder
=
new
Playlist
.
MediaSourceHolder
(
mockMediaSource
,
/* useLazyPreparation= */
false
);
playlist
.
setMediaSources
(
Collections
.
singletonList
(
mediaSourceHolder
),
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
1
));
verify
(
mockMediaSource
,
times
(
0
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
playlist
.
release
();
verify
(
mockMediaSource
,
times
(
0
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
assertThat
(
mediaSourceHolder
.
isRemoved
).
isFalse
();
}
@Test
public
void
testRelease_playlistPrepared_expectSourcesReleasedNotRemoved
()
{
MediaSource
mockMediaSource
=
mock
(
MediaSource
.
class
);
Playlist
.
MediaSourceHolder
mediaSourceHolder
=
new
Playlist
.
MediaSourceHolder
(
mockMediaSource
,
/* useLazyPreparation= */
false
);
playlist
.
prepare
(
/* mediaTransferListener= */
null
);
playlist
.
setMediaSources
(
Collections
.
singletonList
(
mediaSourceHolder
),
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
1
));
verify
(
mockMediaSource
,
times
(
1
))
.
prepareSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
),
/* mediaTransferListener= */
isNull
());
playlist
.
release
();
verify
(
mockMediaSource
,
times
(
1
)).
releaseSource
(
any
(
MediaSource
.
MediaSourceCaller
.
class
));
assertThat
(
mediaSourceHolder
.
isRemoved
).
isFalse
();
}
@Test
public
void
testClearPlaylist_expectSourcesReleasedAndRemoved
()
{
ShuffleOrder
.
DefaultShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
4
);
MediaSource
mockMediaSource1
=
mock
(
MediaSource
.
class
);
MediaSource
mockMediaSource2
=
mock
(
MediaSource
.
class
);
List
<
Playlist
.
MediaSourceHolder
>
holders
=
createFakeHoldersWithSources
(
/* useLazyPreparation= */
false
,
mockMediaSource1
,
mockMediaSource2
);
playlist
.
setMediaSources
(
holders
,
shuffleOrder
);
playlist
.
prepare
(
/* mediaTransferListener= */
null
);
Timeline
timeline
=
playlist
.
clear
(
shuffleOrder
);
assertThat
(
timeline
.
isEmpty
()).
isTrue
();
assertThat
(
holders
.
get
(
0
).
isRemoved
).
isTrue
();
assertThat
(
holders
.
get
(
1
).
isRemoved
).
isTrue
();
verify
(
mockMediaSource1
,
times
(
1
)).
releaseSource
(
any
());
verify
(
mockMediaSource2
,
times
(
1
)).
releaseSource
(
any
());
}
@Test
public
void
testSetMediaSources_expectTimelineUsesCustomShuffleOrder
()
{
Timeline
timeline
=
playlist
.
setMediaSources
(
createFakeHolders
(),
new
FakeShuffleOrder
(
/* length=*/
4
));
assertTimelineUsesFakeShuffleOrder
(
timeline
);
}
@Test
public
void
testAddMediaSources_expectTimelineUsesCustomShuffleOrder
()
{
Timeline
timeline
=
playlist
.
addMediaSources
(
/* index= */
0
,
createFakeHolders
(),
new
FakeShuffleOrder
(
PLAYLIST_SIZE
));
assertTimelineUsesFakeShuffleOrder
(
timeline
);
}
@Test
public
void
testMoveMediaSources_expectTimelineUsesCustomShuffleOrder
()
{
ShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
PLAYLIST_SIZE
);
playlist
.
addMediaSources
(
/* index= */
0
,
createFakeHolders
(),
shuffleOrder
);
Timeline
timeline
=
playlist
.
moveMediaSource
(
/* currentIndex= */
0
,
/* newIndex= */
1
,
new
FakeShuffleOrder
(
PLAYLIST_SIZE
));
assertTimelineUsesFakeShuffleOrder
(
timeline
);
}
@Test
public
void
testMoveMediaSourceRange_expectTimelineUsesCustomShuffleOrder
()
{
ShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
PLAYLIST_SIZE
);
playlist
.
addMediaSources
(
/* index= */
0
,
createFakeHolders
(),
shuffleOrder
);
Timeline
timeline
=
playlist
.
moveMediaSourceRange
(
/* fromIndex= */
0
,
/* toIndex= */
2
,
/* newFromIndex= */
2
,
new
FakeShuffleOrder
(
PLAYLIST_SIZE
));
assertTimelineUsesFakeShuffleOrder
(
timeline
);
}
@Test
public
void
testRemoveMediaSourceRange_expectTimelineUsesCustomShuffleOrder
()
{
ShuffleOrder
shuffleOrder
=
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
PLAYLIST_SIZE
);
playlist
.
addMediaSources
(
/* index= */
0
,
createFakeHolders
(),
shuffleOrder
);
Timeline
timeline
=
playlist
.
removeMediaSourceRange
(
/* fromIndex= */
0
,
/* toIndex= */
2
,
new
FakeShuffleOrder
(
/* length= */
2
));
assertTimelineUsesFakeShuffleOrder
(
timeline
);
}
@Test
public
void
testSetShuffleOrder_expectTimelineUsesCustomShuffleOrder
()
{
playlist
.
setMediaSources
(
createFakeHolders
(),
new
ShuffleOrder
.
DefaultShuffleOrder
(
/* length= */
PLAYLIST_SIZE
));
assertTimelineUsesFakeShuffleOrder
(
playlist
.
setShuffleOrder
(
new
FakeShuffleOrder
(
PLAYLIST_SIZE
)));
}
// Internal methods.
private
static
void
assertTimelineUsesFakeShuffleOrder
(
Timeline
timeline
)
{
assertThat
(
timeline
.
getNextWindowIndex
(
/* windowIndex= */
0
,
Player
.
REPEAT_MODE_OFF
,
/* shuffleModeEnabled= */
true
))
.
isEqualTo
(-
1
);
assertThat
(
timeline
.
getPreviousWindowIndex
(
/* windowIndex= */
timeline
.
getWindowCount
()
-
1
,
Player
.
REPEAT_MODE_OFF
,
/* shuffleModeEnabled= */
true
))
.
isEqualTo
(-
1
);
}
private
static
void
assertDefaultFirstWindowInChildIndexOrder
(
List
<
Playlist
.
MediaSourceHolder
>
holders
)
{
int
[]
indices
=
new
int
[
holders
.
size
()];
for
(
int
i
=
0
;
i
<
indices
.
length
;
i
++)
{
indices
[
i
]
=
i
;
}
assertFirstWindowInChildIndices
(
holders
,
indices
);
}
private
static
void
assertFirstWindowInChildIndices
(
List
<
Playlist
.
MediaSourceHolder
>
holders
,
int
...
firstWindowInChildIndices
)
{
assertThat
(
holders
).
hasSize
(
firstWindowInChildIndices
.
length
);
for
(
int
i
=
0
;
i
<
holders
.
size
();
i
++)
{
assertThat
(
holders
.
get
(
i
).
firstWindowIndexInChild
).
isEqualTo
(
firstWindowInChildIndices
[
i
]);
}
}
private
static
List
<
Playlist
.
MediaSourceHolder
>
createFakeHolders
()
{
MediaSource
fakeMediaSource
=
new
FakeMediaSource
(
new
FakeTimeline
(
1
));
List
<
Playlist
.
MediaSourceHolder
>
holders
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
PLAYLIST_SIZE
;
i
++)
{
holders
.
add
(
new
Playlist
.
MediaSourceHolder
(
fakeMediaSource
,
/* useLazyPreparation= */
true
));
}
return
holders
;
}
private
static
List
<
Playlist
.
MediaSourceHolder
>
createFakeHoldersWithSources
(
boolean
useLazyPreparation
,
MediaSource
...
sources
)
{
List
<
Playlist
.
MediaSourceHolder
>
holders
=
new
ArrayList
<>();
for
(
MediaSource
mediaSource
:
sources
)
{
holders
.
add
(
new
Playlist
.
MediaSourceHolder
(
mediaSource
,
/* useLazyPreparation= */
useLazyPreparation
));
}
return
holders
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment