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
8cc1328d
authored
Jun 14, 2021
by
claincly
Committed by
Oliver Woodman
Jun 15, 2021
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Allow customizing the RtspServer using RtspServerResponseProvider.
PiperOrigin-RevId: 379282201
parent
581e543d
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
467 additions
and
85 deletions
library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java
library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspResponse.java
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspHeadersTest.java
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriodTest.java
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannelTest.java
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtilTest.java
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspServer.java
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspTestUtils.java
testdata/src/test/assets/media/rtsp/h264-dump.json
testdata/src/test/assets/media/rtsp/mp4a-latm-dump.json
library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspHeaders.java
View file @
8cc1328d
...
@@ -66,6 +66,9 @@ import java.util.Map;
...
@@ -66,6 +66,9 @@ import java.util.Map;
public
static
final
String
VIA
=
"via"
;
public
static
final
String
VIA
=
"via"
;
public
static
final
String
WWW_AUTHENTICATE
=
"www-authenticate"
;
public
static
final
String
WWW_AUTHENTICATE
=
"www-authenticate"
;
/** An empty header object. */
public
static
final
RtspHeaders
EMPTY
=
new
RtspHeaders
.
Builder
().
build
();
/** Builds {@link RtspHeaders} instances. */
/** Builds {@link RtspHeaders} instances. */
public
static
final
class
Builder
{
public
static
final
class
Builder
{
private
final
ImmutableListMultimap
.
Builder
<
String
,
String
>
namesAndValuesBuilder
;
private
final
ImmutableListMultimap
.
Builder
<
String
,
String
>
namesAndValuesBuilder
;
...
@@ -76,6 +79,16 @@ import java.util.Map;
...
@@ -76,6 +79,16 @@ import java.util.Map;
}
}
/**
/**
* Creates a new instance to build upon the provided {@link RtspHeaders}.
*
* @param namesAndValuesBuilder A {@link ImmutableListMultimap.Builder} that this builder builds
* upon.
*/
private
Builder
(
ImmutableListMultimap
.
Builder
<
String
,
String
>
namesAndValuesBuilder
)
{
this
.
namesAndValuesBuilder
=
namesAndValuesBuilder
;
}
/**
* Adds a header name and header value pair.
* Adds a header name and header value pair.
*
*
* @param headerName The name of the header.
* @param headerName The name of the header.
...
@@ -130,6 +143,31 @@ import java.util.Map;
...
@@ -130,6 +143,31 @@ import java.util.Map;
private
final
ImmutableListMultimap
<
String
,
String
>
namesAndValues
;
private
final
ImmutableListMultimap
<
String
,
String
>
namesAndValues
;
@Override
public
boolean
equals
(
@Nullable
Object
obj
)
{
if
(
this
==
obj
)
{
return
true
;
}
if
(!(
obj
instanceof
RtspHeaders
))
{
return
false
;
}
RtspHeaders
headers
=
(
RtspHeaders
)
obj
;
return
namesAndValues
.
equals
(
headers
.
namesAndValues
);
}
@Override
public
int
hashCode
()
{
return
namesAndValues
.
hashCode
();
}
/** Returns a {@link Builder} initialized with the values of this instance. */
public
Builder
buildUpon
()
{
ImmutableListMultimap
.
Builder
<
String
,
String
>
namesAndValuesBuilder
=
new
ImmutableListMultimap
.
Builder
<>();
namesAndValuesBuilder
.
putAll
(
namesAndValues
);
return
new
Builder
(
namesAndValuesBuilder
);
}
/**
/**
* Returns a map that associates header names to the list of values associated with the
* Returns a map that associates header names to the list of values associated with the
* corresponding header name.
* corresponding header name.
...
...
library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspResponse.java
View file @
8cc1328d
...
@@ -41,4 +41,14 @@ package com.google.android.exoplayer2.source.rtsp;
...
@@ -41,4 +41,14 @@ package com.google.android.exoplayer2.source.rtsp;
this
.
headers
=
headers
;
this
.
headers
=
headers
;
this
.
messageBody
=
messageBody
;
this
.
messageBody
=
messageBody
;
}
}
/**
* Creates a new instance with an empty {@link #messageBody}.
*
* @param status The status code of this response, as defined in RFC 2326 section 11.
* @param headers The headers of this response.
*/
public
RtspResponse
(
int
status
,
RtspHeaders
headers
)
{
this
(
status
,
headers
,
/* messageBody= */
""
);
}
}
}
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java
View file @
8cc1328d
...
@@ -15,21 +15,20 @@
...
@@ -15,21 +15,20 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
rtsp
;
package
com
.
google
.
android
.
exoplayer2
.
source
.
rtsp
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
android.net.Uri
;
import
android.net.Uri
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
androidx.test.core.app.ApplicationProvider
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.robolectric.RobolectricUtil
;
import
com.google.android.exoplayer2.robolectric.RobolectricUtil
;
import
com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener
;
import
com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener
;
import
com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener
;
import
com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener
;
import
com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException
;
import
com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.collect.ImmutableList
;
import
com.google.common.collect.ImmutableList
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
org.checkerframework.checker.nullness.qual.MonotonicNonNull
;
import
java.util.concurrent.atomic.AtomicReference
;
import
org.junit.After
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.Test
;
...
@@ -41,16 +40,37 @@ import org.robolectric.annotation.internal.DoNotInstrument;
...
@@ -41,16 +40,37 @@ import org.robolectric.annotation.internal.DoNotInstrument;
@DoNotInstrument
@DoNotInstrument
public
final
class
RtspClientTest
{
public
final
class
RtspClientTest
{
private
@MonotonicNonNull
RtspClient
rtspClient
;
private
static
final
String
SESSION_DESCRIPTION
=
private
@MonotonicNonNull
RtspServer
rtspServer
;
"v=0\r\n"
+
"o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
+
"s=Exoplayer test\r\n"
+
"t=0 0\r\n"
+
"a=range:npt=0-50.46\r\n"
;
private
static
final
RtspClient
.
PlaybackEventListener
EMPTY_PLAYBACK_LISTENER
=
new
PlaybackEventListener
()
{
@Override
public
void
onRtspSetupCompleted
()
{}
@Override
public
void
onPlaybackStarted
(
long
startPositionUs
,
ImmutableList
<
RtspTrackTiming
>
trackTimingList
)
{}
@Override
public
void
onPlaybackError
(
RtspPlaybackException
error
)
{}
};
private
ImmutableList
<
RtpPacketStreamDump
>
rtpPacketStreamDumps
;
private
RtspClient
rtspClient
;
private
RtspServer
rtspServer
;
@Before
@Before
public
void
setUp
()
throws
Exception
{
public
void
setUp
()
throws
Exception
{
rtspServer
=
rtpPacketStreamDumps
=
new
RtspServer
(
ImmutableList
.
of
(
RtpPacketStreamDump
.
parse
(
RtspTestUtils
.
readRtpPacketStreamDump
(
"media/rtsp/h264-dump.json"
),
TestUtil
.
getString
(
RtspTestUtils
.
readRtpPacketStreamDump
(
"media/rtsp/aac-dump.json"
),
ApplicationProvider
.
getApplicationContext
(),
"media/rtsp/aac-dump.json"
)));
// MP4A-LATM is not supported at the moment.
RtspTestUtils
.
readRtpPacketStreamDump
(
"media/rtsp/mp4a-latm-dump.json"
));
}
}
@After
@After
...
@@ -60,40 +80,173 @@ public final class RtspClientTest {
...
@@ -60,40 +80,173 @@ public final class RtspClientTest {
}
}
@Test
@Test
public
void
connectServerAndClient_
withS
erverSupportsDescribe_updatesSessionTimeline
()
public
void
connectServerAndClient_
s
erverSupportsDescribe_updatesSessionTimeline
()
throws
Exception
{
throws
Exception
{
int
serverRtspPortNumber
=
checkNotNull
(
rtspServer
).
startAndGetPortNumber
();
class
ResponseProvider
implements
RtspServer
.
ResponseProvider
{
@Override
public
RtspResponse
getOptionsResponse
()
{
return
new
RtspResponse
(
/* status= */
200
,
new
RtspHeaders
.
Builder
().
add
(
RtspHeaders
.
PUBLIC
,
"OPTIONS, DESCRIBE"
).
build
());
}
@Override
public
RtspResponse
getDescribeResponse
(
Uri
requestedUri
)
{
return
RtspTestUtils
.
newDescribeResponseWithSdpMessage
(
SESSION_DESCRIPTION
,
rtpPacketStreamDumps
,
requestedUri
);
}
}
rtspServer
=
new
RtspServer
(
new
ResponseProvider
());
Atomic
Boolean
sessionTimelineUpdateEventReceived
=
new
AtomicBoolean
();
Atomic
Reference
<
ImmutableList
<
RtspMediaTrack
>>
tracksInSession
=
new
AtomicReference
<>
();
rtspClient
=
rtspClient
=
new
RtspClient
(
new
RtspClient
(
new
SessionInfoListener
()
{
new
SessionInfoListener
()
{
@Override
@Override
public
void
onSessionTimelineUpdated
(
public
void
onSessionTimelineUpdated
(
RtspSessionTiming
timing
,
ImmutableList
<
RtspMediaTrack
>
tracks
)
{
RtspSessionTiming
timing
,
ImmutableList
<
RtspMediaTrack
>
tracks
)
{
sessionTimelineUpdateEventReceived
.
set
(!
tracks
.
isEmpty
()
);
tracksInSession
.
set
(
tracks
);
}
}
@Override
@Override
public
void
onSessionTimelineRequestFailed
(
public
void
onSessionTimelineRequestFailed
(
String
message
,
@Nullable
Throwable
cause
)
{}
String
message
,
@Nullable
Throwable
cause
)
{}
},
},
new
PlaybackEventListener
()
{
EMPTY_PLAYBACK_LISTENER
,
/* userAgent= */
"ExoPlayer:RtspClientTest"
,
RtspTestUtils
.
getTestUri
(
rtspServer
.
startAndGetPortNumber
()));
rtspClient
.
start
();
RobolectricUtil
.
runMainLooperUntil
(()
->
tracksInSession
.
get
()
!=
null
);
assertThat
(
tracksInSession
.
get
()).
hasSize
(
2
);
}
@Test
public
void
connectServerAndClient_serverSupportsDescribeNoHeaderInOptions_updatesSessionTimeline
()
throws
Exception
{
class
ResponseProvider
implements
RtspServer
.
ResponseProvider
{
@Override
public
RtspResponse
getOptionsResponse
()
{
return
new
RtspResponse
(
/* status= */
200
,
RtspHeaders
.
EMPTY
);
}
@Override
public
RtspResponse
getDescribeResponse
(
Uri
requestedUri
)
{
return
RtspTestUtils
.
newDescribeResponseWithSdpMessage
(
SESSION_DESCRIPTION
,
rtpPacketStreamDumps
,
requestedUri
);
}
}
rtspServer
=
new
RtspServer
(
new
ResponseProvider
());
AtomicReference
<
ImmutableList
<
RtspMediaTrack
>>
tracksInSession
=
new
AtomicReference
<>();
rtspClient
=
new
RtspClient
(
new
SessionInfoListener
()
{
@Override
@Override
public
void
onRtspSetupCompleted
()
{}
public
void
onSessionTimelineUpdated
(
RtspSessionTiming
timing
,
ImmutableList
<
RtspMediaTrack
>
tracks
)
{
tracksInSession
.
set
(
tracks
);
}
@Override
@Override
public
void
onPlaybackStarted
(
public
void
onSessionTimelineRequestFailed
(
long
startPositionUs
,
ImmutableList
<
RtspTrackTiming
>
trackTimingList
)
{}
String
message
,
@Nullable
Throwable
cause
)
{}
},
EMPTY_PLAYBACK_LISTENER
,
/* userAgent= */
"ExoPlayer:RtspClientTest"
,
RtspTestUtils
.
getTestUri
(
rtspServer
.
startAndGetPortNumber
()));
rtspClient
.
start
();
RobolectricUtil
.
runMainLooperUntil
(()
->
tracksInSession
.
get
()
!=
null
);
assertThat
(
tracksInSession
.
get
()).
hasSize
(
2
);
}
@Test
public
void
connectServerAndClient_serverDoesNotSupportDescribe_doesNotUpdateTimeline
()
throws
Exception
{
AtomicBoolean
clientHasSentDescribeRequest
=
new
AtomicBoolean
();
class
ResponseProvider
implements
RtspServer
.
ResponseProvider
{
@Override
public
RtspResponse
getOptionsResponse
()
{
return
new
RtspResponse
(
/* status= */
200
,
new
RtspHeaders
.
Builder
().
add
(
RtspHeaders
.
PUBLIC
,
"OPTIONS"
).
build
());
}
@Override
public
RtspResponse
getDescribeResponse
(
Uri
requestedUri
)
{
clientHasSentDescribeRequest
.
set
(
true
);
return
RtspTestUtils
.
RTSP_ERROR_METHOD_NOT_ALLOWED
;
}
}
rtspServer
=
new
RtspServer
(
new
ResponseProvider
());
AtomicReference
<
String
>
failureMessage
=
new
AtomicReference
<>();
rtspClient
=
new
RtspClient
(
new
SessionInfoListener
()
{
@Override
public
void
onSessionTimelineUpdated
(
RtspSessionTiming
timing
,
ImmutableList
<
RtspMediaTrack
>
tracks
)
{}
@Override
public
void
onSessionTimelineRequestFailed
(
String
message
,
@Nullable
Throwable
cause
)
{
failureMessage
.
set
(
message
);
}
},
EMPTY_PLAYBACK_LISTENER
,
/* userAgent= */
"ExoPlayer:RtspClientTest"
,
RtspTestUtils
.
getTestUri
(
rtspServer
.
startAndGetPortNumber
()));
rtspClient
.
start
();
RobolectricUtil
.
runMainLooperUntil
(()
->
failureMessage
.
get
()
!=
null
);
assertThat
(
failureMessage
.
get
()).
contains
(
"DESCRIBE not supported."
);
assertThat
(
clientHasSentDescribeRequest
.
get
()).
isFalse
();
}
@Test
public
void
connectServerAndClient_malformedSdpInDescribeResponse_doesNotUpdateTimeline
()
throws
Exception
{
class
ResponseProvider
implements
RtspServer
.
ResponseProvider
{
@Override
public
RtspResponse
getOptionsResponse
()
{
return
new
RtspResponse
(
/* status= */
200
,
new
RtspHeaders
.
Builder
().
add
(
RtspHeaders
.
PUBLIC
,
"OPTIONS, DESCRIBE"
).
build
());
}
@Override
public
RtspResponse
getDescribeResponse
(
Uri
requestedUri
)
{
// This session description misses required the o, t and s tags.
return
RtspTestUtils
.
newDescribeResponseWithSdpMessage
(
/* sessionDescription= */
"v=0\r\n"
,
rtpPacketStreamDumps
,
requestedUri
);
}
}
rtspServer
=
new
RtspServer
(
new
ResponseProvider
());
AtomicReference
<
Throwable
>
failureCause
=
new
AtomicReference
<>();
rtspClient
=
new
RtspClient
(
new
SessionInfoListener
()
{
@Override
@Override
public
void
onPlaybackError
(
RtspPlaybackException
error
)
{}
public
void
onSessionTimelineUpdated
(
RtspSessionTiming
timing
,
ImmutableList
<
RtspMediaTrack
>
tracks
)
{}
@Override
public
void
onSessionTimelineRequestFailed
(
String
message
,
@Nullable
Throwable
cause
)
{
failureCause
.
set
(
cause
);
}
},
},
EMPTY_PLAYBACK_LISTENER
,
/* userAgent= */
"ExoPlayer:RtspClientTest"
,
/* userAgent= */
"ExoPlayer:RtspClientTest"
,
/* uri= */
Uri
.
parse
(
RtspTestUtils
.
getTestUri
(
rtspServer
.
startAndGetPortNumber
()));
Util
.
formatInvariant
(
"rtsp://localhost:%d/test"
,
serverRtspPortNumber
)));
rtspClient
.
start
();
rtspClient
.
start
();
RobolectricUtil
.
runMainLooperUntil
(
sessionTimelineUpdateEventReceived:
:
get
);
RobolectricUtil
.
runMainLooperUntil
(()
->
failureCause
.
get
()
!=
null
);
assertThat
(
failureCause
.
get
()).
hasCauseThat
().
isInstanceOf
(
ParserException
.
class
);
}
}
}
}
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspHeadersTest.java
View file @
8cc1328d
...
@@ -67,6 +67,31 @@ public final class RtspHeadersTest {
...
@@ -67,6 +67,31 @@ public final class RtspHeadersTest {
}
}
@Test
@Test
public
void
buildUpon_createEqualHeaders
()
{
RtspHeaders
headers
=
new
RtspHeaders
.
Builder
()
.
addAll
(
ImmutableMap
.
of
(
"Content-Length"
,
"707"
,
"Transport"
,
"RTP/AVP;unicast;client_port=65458-65459\r\n"
))
.
build
();
assertThat
(
headers
.
buildUpon
().
build
()).
isEqualTo
(
headers
);
}
@Test
public
void
buildUpon_buildsUponExistingHeaders
()
{
RtspHeaders
headers
=
new
RtspHeaders
.
Builder
().
add
(
"Content-Length"
,
"707"
).
build
();
assertThat
(
headers
.
buildUpon
().
add
(
"Content-Encoding"
,
"utf-8"
).
build
())
.
isEqualTo
(
new
RtspHeaders
.
Builder
()
.
add
(
"Content-Length"
,
"707"
)
.
add
(
"Content-Encoding"
,
"utf-8"
)
.
build
());
}
@Test
public
void
get_getsHeaderValuesCaseInsensitively
()
{
public
void
get_getsHeaderValuesCaseInsensitively
()
{
RtspHeaders
headers
=
RtspHeaders
headers
=
new
RtspHeaders
.
Builder
()
new
RtspHeaders
.
Builder
()
...
@@ -144,7 +169,8 @@ public final class RtspHeadersTest {
...
@@ -144,7 +169,8 @@ public final class RtspHeadersTest {
}
}
@Test
@Test
public
void
asMap_withMultipleValuesMappedToTheSameName_getsTheMappedValuesInAdditionOrder
()
{
public
void
asMultiMap_withMultipleValuesMappedToTheSameName_getsTheMappedValuesInAdditionOrder
()
{
RtspHeaders
headers
=
RtspHeaders
headers
=
new
RtspHeaders
.
Builder
()
new
RtspHeaders
.
Builder
()
.
addAll
(
.
addAll
(
...
...
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriodTest.java
0 → 100644
View file @
8cc1328d
/*
* Copyright 2021 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
.
source
.
rtsp
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
android.net.Uri
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.robolectric.RobolectricUtil
;
import
com.google.android.exoplayer2.source.MediaPeriod
;
import
com.google.android.exoplayer2.upstream.DefaultAllocator
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.collect.ImmutableList
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.concurrent.atomic.AtomicLong
;
import
org.junit.After
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.annotation.internal.DoNotInstrument
;
/** Tests the {@link RtspMediaPeriod} using the {@link RtspServer}. */
@RunWith
(
AndroidJUnit4
.
class
)
@DoNotInstrument
public
final
class
RtspMediaPeriodTest
{
private
RtspMediaPeriod
mediaPeriod
;
private
RtspServer
rtspServer
;
@After
public
void
tearDown
()
{
Util
.
closeQuietly
(
rtspServer
);
}
@Test
public
void
prepareMediaPeriod_refreshesSourceInfoAndCallsOnPrepared
()
throws
Exception
{
RtpPacketStreamDump
rtpPacketStreamDump
=
RtspTestUtils
.
readRtpPacketStreamDump
(
"media/rtsp/aac-dump.json"
);
rtspServer
=
new
RtspServer
(
new
RtspServer
.
ResponseProvider
()
{
@Override
public
RtspResponse
getOptionsResponse
()
{
return
new
RtspResponse
(
/* status= */
200
,
new
RtspHeaders
.
Builder
().
add
(
RtspHeaders
.
PUBLIC
,
"OPTIONS, DESCRIBE"
).
build
());
}
@Override
public
RtspResponse
getDescribeResponse
(
Uri
requestedUri
)
{
return
RtspTestUtils
.
newDescribeResponseWithSdpMessage
(
"v=0\r\n"
+
"o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
+
"s=Exoplayer test\r\n"
+
"t=0 0\r\n"
// The session is 50.46s long.
+
"a=range:npt=0-50.46\r\n"
,
ImmutableList
.
of
(
rtpPacketStreamDump
),
requestedUri
);
}
});
AtomicBoolean
prepareCallbackCalled
=
new
AtomicBoolean
();
AtomicLong
refreshedSourceDurationMs
=
new
AtomicLong
();
mediaPeriod
=
new
RtspMediaPeriod
(
new
DefaultAllocator
(
/* trimOnReset= */
true
,
C
.
DEFAULT_BUFFER_SEGMENT_SIZE
),
new
TransferRtpDataChannelFactory
(),
RtspTestUtils
.
getTestUri
(
rtspServer
.
startAndGetPortNumber
()),
/* listener= */
timing
->
refreshedSourceDurationMs
.
set
(
timing
.
getDurationMs
()),
/* userAgent= */
"ExoPlayer:RtspPeriodTest"
);
mediaPeriod
.
prepare
(
new
MediaPeriod
.
Callback
()
{
@Override
public
void
onPrepared
(
MediaPeriod
mediaPeriod
)
{
prepareCallbackCalled
.
set
(
true
);
}
@Override
public
void
onContinueLoadingRequested
(
MediaPeriod
source
)
{
source
.
continueLoading
(
/* positionUs= */
0
);
}
},
/* positionUs= */
0
);
RobolectricUtil
.
runMainLooperUntil
(
prepareCallbackCalled:
:
get
);
mediaPeriod
.
release
();
assertThat
(
refreshedSourceDurationMs
.
get
()).
isEqualTo
(
50_460
);
}
}
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageChannelTest.java
View file @
8cc1328d
...
@@ -55,8 +55,7 @@ public final class RtspMessageChannelTest {
...
@@ -55,8 +55,7 @@ public final class RtspMessageChannelTest {
new
RtspHeaders
.
Builder
()
new
RtspHeaders
.
Builder
()
.
add
(
RtspHeaders
.
CSEQ
,
"2"
)
.
add
(
RtspHeaders
.
CSEQ
,
"2"
)
.
add
(
RtspHeaders
.
PUBLIC
,
"OPTIONS"
)
.
add
(
RtspHeaders
.
PUBLIC
,
"OPTIONS"
)
.
build
(),
.
build
());
""
);
RtspResponse
describeResponse
=
RtspResponse
describeResponse
=
new
RtspResponse
(
new
RtspResponse
(
...
@@ -84,8 +83,7 @@ public final class RtspMessageChannelTest {
...
@@ -84,8 +83,7 @@ public final class RtspMessageChannelTest {
new
RtspHeaders
.
Builder
()
new
RtspHeaders
.
Builder
()
.
add
(
RtspHeaders
.
CSEQ
,
"5"
)
.
add
(
RtspHeaders
.
CSEQ
,
"5"
)
.
add
(
RtspHeaders
.
TRANSPORT
,
"RTP/AVP/TCP;unicast;interleaved=0-1"
)
.
add
(
RtspHeaders
.
TRANSPORT
,
"RTP/AVP/TCP;unicast;interleaved=0-1"
)
.
build
(),
.
build
());
""
);
// Channel: 0, size: 5, data: 01 02 03 04 05.
// Channel: 0, size: 5, data: 01 02 03 04 05.
byte
[]
interleavedData1
=
Util
.
getBytesFromHexString
(
"0000050102030405"
);
byte
[]
interleavedData1
=
Util
.
getBytesFromHexString
(
"0000050102030405"
);
...
...
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMessageUtilTest.java
View file @
8cc1328d
...
@@ -250,8 +250,7 @@ public final class RtspMessageUtilTest {
...
@@ -250,8 +250,7 @@ public final class RtspMessageUtilTest {
"4"
,
"4"
,
RtspHeaders
.
TRANSPORT
,
RtspHeaders
.
TRANSPORT
,
"RTP/AVP;unicast;client_port=65458-65459;server_port=5354-5355"
))
"RTP/AVP;unicast;client_port=65458-65459;server_port=5354-5355"
))
.
build
(),
.
build
());
/* messageBody= */
""
);
List
<
String
>
messageLines
=
RtspMessageUtil
.
serializeResponse
(
response
);
List
<
String
>
messageLines
=
RtspMessageUtil
.
serializeResponse
(
response
);
List
<
String
>
expectedLines
=
List
<
String
>
expectedLines
=
...
@@ -340,9 +339,7 @@ public final class RtspMessageUtilTest {
...
@@ -340,9 +339,7 @@ public final class RtspMessageUtilTest {
public
void
serialize_failedResponse_succeeds
()
{
public
void
serialize_failedResponse_succeeds
()
{
RtspResponse
response
=
RtspResponse
response
=
new
RtspResponse
(
new
RtspResponse
(
/* status= */
454
,
/* status= */
454
,
new
RtspHeaders
.
Builder
().
add
(
RtspHeaders
.
CSEQ
,
"4"
).
build
());
new
RtspHeaders
.
Builder
().
add
(
RtspHeaders
.
CSEQ
,
"4"
).
build
(),
/* messageBody= */
""
);
List
<
String
>
messageLines
=
RtspMessageUtil
.
serializeResponse
(
response
);
List
<
String
>
messageLines
=
RtspMessageUtil
.
serializeResponse
(
response
);
List
<
String
>
expectedLines
=
Arrays
.
asList
(
"RTSP/1.0 454 Session Not Found"
,
"cseq: 4"
,
""
,
""
);
List
<
String
>
expectedLines
=
Arrays
.
asList
(
"RTSP/1.0 454 Session Not Found"
,
"cseq: 4"
,
""
,
""
);
...
...
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspServer.java
View file @
8cc1328d
...
@@ -23,7 +23,6 @@ import android.net.Uri;
...
@@ -23,7 +23,6 @@ import android.net.Uri;
import
android.os.Handler
;
import
android.os.Handler
;
import
android.os.Looper
;
import
android.os.Looper
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.collect.ImmutableMap
;
import
java.io.Closeable
;
import
java.io.Closeable
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.net.InetAddress
;
import
java.net.InetAddress
;
...
@@ -31,31 +30,28 @@ import java.net.ServerSocket;
...
@@ -31,31 +30,28 @@ import java.net.ServerSocket;
import
java.net.Socket
;
import
java.net.Socket
;
import
java.net.SocketException
;
import
java.net.SocketException
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
org.checkerframework.checker.nullness.qual.MonotonicNonNull
;
import
org.checkerframework.checker.nullness.qual.MonotonicNonNull
;
/** The RTSP server. */
/** The RTSP server. */
public
final
class
RtspServer
implements
Closeable
{
public
final
class
RtspServer
implements
Closeable
{
private
static
final
String
PUBLIC_SUPPORTED_METHODS
=
"OPTIONS, DESCRIBE"
;
/** Provides RTSP response. */
public
interface
ResponseProvider
{
/** RTSP error Method Not Allowed (RFC2326 Section 7.1.1)
. */
/** Returns an RTSP OPTIONS {@link RtspResponse response}
. */
private
static
final
int
STATUS_OK
=
200
;
RtspResponse
getOptionsResponse
()
;
private
static
final
int
STATUS_METHOD_NOT_ALLOWED
=
405
;
/** Returns an RTSP DESCRIBE {@link RtspResponse response}. */
default
RtspResponse
getDescribeResponse
(
Uri
requestedUri
)
{
private
static
final
String
SESSION_DESCRIPTION
=
return
RtspTestUtils
.
RTSP_ERROR_METHOD_NOT_ALLOWED
;
"v=0\r\n"
}
+
"o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
}
+
"s=Exoplayer test\r\n"
+
"t=0 0\r\n"
+
"a=range:npt=0-50.46\r\n"
;
private
final
Thread
listenerThread
;
private
final
Thread
listenerThread
;
/** Runs on the thread on which the constructor was called. */
/** Runs on the thread on which the constructor was called. */
private
final
Handler
mainHandler
;
private
final
Handler
mainHandler
;
private
final
R
tpPacketStreamDump
rtpPacketStreamDump
;
private
final
R
esponseProvider
responseProvider
;
private
@MonotonicNonNull
ServerSocket
serverSocket
;
private
@MonotonicNonNull
ServerSocket
serverSocket
;
private
@MonotonicNonNull
RtspMessageChannel
connectedClient
;
private
@MonotonicNonNull
RtspMessageChannel
connectedClient
;
...
@@ -67,11 +63,11 @@ public final class RtspServer implements Closeable {
...
@@ -67,11 +63,11 @@ public final class RtspServer implements Closeable {
*
*
* <p>The constructor must be called on a {@link Looper} thread.
* <p>The constructor must be called on a {@link Looper} thread.
*/
*/
public
RtspServer
(
RtpPacketStreamDump
rtpPacketStreamDump
)
{
public
RtspServer
(
ResponseProvider
responseProvider
)
{
this
.
rtpPacketStreamDump
=
rtpPacketStreamDump
;
listenerThread
=
listenerThread
=
new
Thread
(
this
::
listenToIncomingRtspConnection
,
"ExoPlayerTest:RtspConnectionMonitor"
);
new
Thread
(
this
::
listenToIncomingRtspConnection
,
"ExoPlayerTest:RtspConnectionMonitor"
);
mainHandler
=
Util
.
createHandlerForCurrentLooper
();
mainHandler
=
Util
.
createHandlerForCurrentLooper
();
this
.
responseProvider
=
responseProvider
;
}
}
/**
/**
...
@@ -123,54 +119,25 @@ public final class RtspServer implements Closeable {
...
@@ -123,54 +119,25 @@ public final class RtspServer implements Closeable {
String
cSeq
=
checkNotNull
(
request
.
headers
.
get
(
RtspHeaders
.
CSEQ
));
String
cSeq
=
checkNotNull
(
request
.
headers
.
get
(
RtspHeaders
.
CSEQ
));
switch
(
request
.
method
)
{
switch
(
request
.
method
)
{
case
METHOD_OPTIONS:
case
METHOD_OPTIONS:
onOptionsRequestReceived
(
cSeq
);
sendResponse
(
responseProvider
.
getOptionsResponse
(),
cSeq
);
break
;
break
;
case
METHOD_DESCRIBE:
case
METHOD_DESCRIBE:
onDescribeRequestReceived
(
request
.
uri
,
cSeq
);
sendResponse
(
responseProvider
.
getDescribeResponse
(
request
.
uri
)
,
cSeq
);
break
;
break
;
default
:
default
:
send
ErrorResponse
(
STATUS
_METHOD_NOT_ALLOWED
,
cSeq
);
send
Response
(
RtspTestUtils
.
RTSP_ERROR
_METHOD_NOT_ALLOWED
,
cSeq
);
}
}
}
}
private
void
onOptionsRequestReceived
(
String
cSeq
)
{
private
void
sendResponse
(
RtspResponse
response
,
String
cSeq
)
{
sendResponseWithCommonHeaders
(
/* status= */
STATUS_OK
,
/* cSeq= */
cSeq
,
/* additionalHeaders= */
ImmutableMap
.
of
(
RtspHeaders
.
PUBLIC
,
PUBLIC_SUPPORTED_METHODS
),
/* messageBody= */
""
);
}
private
void
onDescribeRequestReceived
(
Uri
requestedUri
,
String
cSeq
)
{
String
sdpMessage
=
SESSION_DESCRIPTION
+
rtpPacketStreamDump
.
mediaDescription
+
"\r\n"
;
sendResponseWithCommonHeaders
(
/* status= */
STATUS_OK
,
/* cSeq= */
cSeq
,
/* additionalHeaders= */
ImmutableMap
.
of
(
RtspHeaders
.
CONTENT_BASE
,
requestedUri
.
toString
(),
RtspHeaders
.
CONTENT_TYPE
,
"application/sdp"
,
RtspHeaders
.
CONTENT_LENGTH
,
String
.
valueOf
(
sdpMessage
.
length
())),
/* messageBody= */
sdpMessage
);
}
private
void
sendErrorResponse
(
int
status
,
String
cSeq
)
{
sendResponseWithCommonHeaders
(
status
,
cSeq
,
/* additionalHeaders= */
ImmutableMap
.
of
(),
/* messageBody= */
""
);
}
private
void
sendResponseWithCommonHeaders
(
int
status
,
String
cSeq
,
Map
<
String
,
String
>
additionalHeaders
,
String
messageBody
)
{
RtspHeaders
.
Builder
headerBuilder
=
new
RtspHeaders
.
Builder
();
headerBuilder
.
add
(
RtspHeaders
.
CSEQ
,
cSeq
);
headerBuilder
.
addAll
(
additionalHeaders
);
connectedClient
.
send
(
connectedClient
.
send
(
RtspMessageUtil
.
serializeResponse
(
RtspMessageUtil
.
serializeResponse
(
new
RtspResponse
(
new
RtspResponse
(
/* status= */
status
,
response
.
status
,
/* headers= */
headerBuilder
.
build
(),
response
.
headers
.
buildUpon
().
add
(
RtspHeaders
.
CSEQ
,
cSeq
)
.
build
(),
/* messageBody= */
messageBody
)));
response
.
messageBody
)));
}
}
}
}
...
...
library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspTestUtils.java
0 → 100644
View file @
8cc1328d
/*
* Copyright 2021 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
.
source
.
rtsp
;
import
android.net.Uri
;
import
androidx.test.core.app.ApplicationProvider
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.util.List
;
/** Utility methods for RTSP tests. */
/* package */
final
class
RtspTestUtils
{
/** RTSP error Method Not Allowed (RFC2326 Section 7.1.1). */
public
static
final
RtspResponse
RTSP_ERROR_METHOD_NOT_ALLOWED
=
new
RtspResponse
(
454
,
RtspHeaders
.
EMPTY
);
/**
* Parses and returns an {@link RtpPacketStreamDump} from the file identified by {@code filepath}.
*
* <p>See {@link RtpPacketStreamDump#parse} for details on the dump file format.
*/
public
static
RtpPacketStreamDump
readRtpPacketStreamDump
(
String
filepath
)
throws
IOException
{
return
RtpPacketStreamDump
.
parse
(
TestUtil
.
getString
(
ApplicationProvider
.
getApplicationContext
(),
filepath
));
}
/** Returns an {@link RtspResponse} with a SDP message body. */
public
static
RtspResponse
newDescribeResponseWithSdpMessage
(
String
sessionDescription
,
List
<
RtpPacketStreamDump
>
rtpPacketStreamDumps
,
Uri
requestedUri
)
{
StringBuilder
sdpMessageBuilder
=
new
StringBuilder
(
sessionDescription
);
for
(
RtpPacketStreamDump
rtpPacketStreamDump
:
rtpPacketStreamDumps
)
{
sdpMessageBuilder
.
append
(
rtpPacketStreamDump
.
mediaDescription
).
append
(
"\r\n"
);
}
String
sdpMessage
=
sdpMessageBuilder
.
toString
();
return
new
RtspResponse
(
200
,
new
RtspHeaders
.
Builder
()
.
add
(
RtspHeaders
.
CONTENT_BASE
,
requestedUri
.
toString
())
.
add
(
RtspHeaders
.
CONTENT_LENGTH
,
String
.
valueOf
(
sdpMessage
.
getBytes
(
RtspMessageChannel
.
CHARSET
).
length
))
.
build
(),
/* messageBody= */
sdpMessage
);
}
/** Returns the test RTSP {@link Uri}. */
public
static
Uri
getTestUri
(
int
serverRtspPortNumber
)
{
return
Uri
.
parse
(
Util
.
formatInvariant
(
"rtsp://localhost:%d/test"
,
serverRtspPortNumber
));
}
private
RtspTestUtils
()
{}
}
testdata/src/test/assets/media/rtsp/h264-dump.json
0 → 100644
View file @
8cc1328d
{
"trackName"
:
"track1"
,
"firstSequenceNumber"
:
0
,
"firstTimestamp"
:
0
,
"transmitIntervalMs"
:
30
,
"mediaDescription"
:
"m=video 0 RTP/AVP 96
\r\n
c=IN IP4 0.0.0.0
\r\n
b=AS:500
\r\n
a=rtpmap:96 H264/90000
\r\n
a=fmtp:96 packetization-mode=1;profile-level-id=4dE01E;sprop-parameter-sets=Z01AHpZ2BQHtgKBAAAOpgACvyA0YAgQAgRe98HhEI3A=,aN48gA==
\r\n
a=control:track1
\r\n
"
,
"packets"
:
[
]
}
testdata/src/test/assets/media/rtsp/mp4a-latm-dump.json
0 → 100644
View file @
8cc1328d
{
"trackName"
:
"track3"
,
"firstSequenceNumber"
:
0
,
"firstTimestamp"
:
0
,
"transmitIntervalMs"
:
30
,
"mediaDescription"
:
"m=audio 0 RTP/AVP 97
\r\n
c=IN IP4 0.0.0.0
\r\n
b=AS:61
\r\n
a=rtpmap:97 MP4A-LATM/44100/2
\r\n
a=fmtp:97 profile-level-id=15;object=2;cpresent=0;config=400024203FC0
\r\n
a=control:track3
\r\n
"
,
"packets"
:
[
]
}
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