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
b1e56304
authored
Jun 08, 2020
by
kimvde
Committed by
Ian Baker
Jun 08, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add support for inferring file format from MIME type
PiperOrigin-RevId: 315283926
parent
99d805f6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
174 additions
and
39 deletions
library/common/src/main/java/com/google/android/exoplayer2/util/FileTypes.java
library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
library/common/src/test/java/com/google/android/exoplayer2/util/FileTypesTest.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java
library/common/src/main/java/com/google/android/exoplayer2/util/FileTypes.java
View file @
b1e56304
...
...
@@ -15,11 +15,17 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
util
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
MimeTypes
.
normalizeMimeType
;
import
android.net.Uri
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.List
;
import
java.util.Map
;
/** Defines common file type constants and helper methods. */
public
final
class
FileTypes
{
...
...
@@ -64,6 +70,8 @@ public final class FileTypes {
/** File type for the WebVTT format. */
public
static
final
int
WEBVTT
=
13
;
@VisibleForTesting
/* package */
static
final
String
HEADER_CONTENT_TYPE
=
"Content-Type"
;
private
static
final
String
EXTENSION_AC3
=
".ac3"
;
private
static
final
String
EXTENSION_EC3
=
".ec3"
;
private
static
final
String
EXTENSION_AC4
=
".ac4"
;
...
...
@@ -94,13 +102,72 @@ public final class FileTypes {
private
FileTypes
()
{}
/** Returns the {@link Type} corresponding to the response headers provided. */
@FileTypes
.
Type
public
static
int
inferFileTypeFromResponseHeaders
(
Map
<
String
,
List
<
String
>>
responseHeaders
)
{
@Nullable
List
<
String
>
contentTypes
=
responseHeaders
.
get
(
HEADER_CONTENT_TYPE
);
@Nullable
String
mimeType
=
contentTypes
==
null
||
contentTypes
.
isEmpty
()
?
null
:
contentTypes
.
get
(
0
);
return
inferFileTypeFromMimeType
(
mimeType
);
}
/**
* Returns the {@link Type} corresponding to the filename extension of the provided {@link Uri}.
* The filename is considered to be the last segment of the {@link Uri} path.
* Returns the {@link Type} corresponding to the MIME type provided.
*
* <p>Returns {@link #UNKNOWN} if the mime type is {@code null}.
*/
@FileTypes
.
Type
public
static
int
getFormatFromExtension
(
Uri
uri
)
{
String
filename
=
uri
.
getLastPathSegment
();
public
static
int
inferFileTypeFromMimeType
(
@Nullable
String
mimeType
)
{
if
(
mimeType
==
null
)
{
return
FileTypes
.
UNKNOWN
;
}
mimeType
=
normalizeMimeType
(
mimeType
);
switch
(
mimeType
)
{
case
MimeTypes
.
AUDIO_AC3
:
case
MimeTypes
.
AUDIO_E_AC3
:
case
MimeTypes
.
AUDIO_E_AC3_JOC
:
return
FileTypes
.
AC3
;
case
MimeTypes
.
AUDIO_AC4
:
return
FileTypes
.
AC4
;
case
MimeTypes
.
AUDIO_AMR
:
case
MimeTypes
.
AUDIO_AMR_NB
:
case
MimeTypes
.
AUDIO_AMR_WB
:
return
FileTypes
.
AMR
;
case
MimeTypes
.
AUDIO_FLAC
:
return
FileTypes
.
FLAC
;
case
MimeTypes
.
VIDEO_FLV
:
return
FileTypes
.
FLV
;
case
MimeTypes
.
VIDEO_MATROSKA
:
case
MimeTypes
.
AUDIO_MATROSKA
:
case
MimeTypes
.
VIDEO_WEBM
:
case
MimeTypes
.
AUDIO_WEBM
:
case
MimeTypes
.
APPLICATION_WEBM
:
return
FileTypes
.
MATROSKA
;
case
MimeTypes
.
AUDIO_MPEG
:
return
FileTypes
.
MP3
;
case
MimeTypes
.
VIDEO_MP4
:
case
MimeTypes
.
AUDIO_MP4
:
case
MimeTypes
.
APPLICATION_MP4
:
return
FileTypes
.
MP4
;
case
MimeTypes
.
AUDIO_OGG
:
return
FileTypes
.
OGG
;
case
MimeTypes
.
VIDEO_PS
:
return
FileTypes
.
PS
;
case
MimeTypes
.
VIDEO_MP2T
:
return
FileTypes
.
TS
;
case
MimeTypes
.
AUDIO_WAV
:
return
FileTypes
.
WAV
;
case
MimeTypes
.
TEXT_VTT
:
return
FileTypes
.
WEBVTT
;
default
:
return
FileTypes
.
UNKNOWN
;
}
}
/** Returns the {@link Type} corresponding to the {@link Uri} provided. */
@FileTypes
.
Type
public
static
int
inferFileTypeFromUri
(
Uri
uri
)
{
@Nullable
String
filename
=
uri
.
getLastPathSegment
();
if
(
filename
==
null
)
{
return
FileTypes
.
UNKNOWN
;
}
else
if
(
filename
.
endsWith
(
EXTENSION_AC3
)
||
filename
.
endsWith
(
EXTENSION_EC3
))
{
...
...
library/common/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java
View file @
b1e56304
...
...
@@ -47,6 +47,7 @@ public final class MimeTypes {
public
static
final
String
BASE_TYPE_APPLICATION
=
"application"
;
public
static
final
String
VIDEO_MP4
=
BASE_TYPE_VIDEO
+
"/mp4"
;
public
static
final
String
VIDEO_MATROSKA
=
BASE_TYPE_VIDEO
+
"/x-matroska"
;
public
static
final
String
VIDEO_WEBM
=
BASE_TYPE_VIDEO
+
"/webm"
;
public
static
final
String
VIDEO_H263
=
BASE_TYPE_VIDEO
+
"/3gpp"
;
public
static
final
String
VIDEO_H264
=
BASE_TYPE_VIDEO
+
"/avc"
;
...
...
@@ -67,6 +68,7 @@ public final class MimeTypes {
public
static
final
String
AUDIO_MP4
=
BASE_TYPE_AUDIO
+
"/mp4"
;
public
static
final
String
AUDIO_AAC
=
BASE_TYPE_AUDIO
+
"/mp4a-latm"
;
public
static
final
String
AUDIO_MATROSKA
=
BASE_TYPE_AUDIO
+
"/x-matroska"
;
public
static
final
String
AUDIO_WEBM
=
BASE_TYPE_AUDIO
+
"/webm"
;
public
static
final
String
AUDIO_MPEG
=
BASE_TYPE_AUDIO
+
"/mpeg"
;
public
static
final
String
AUDIO_MPEG_L1
=
BASE_TYPE_AUDIO
+
"/mpeg-L1"
;
...
...
@@ -91,6 +93,7 @@ public final class MimeTypes {
public
static
final
String
AUDIO_ALAC
=
BASE_TYPE_AUDIO
+
"/alac"
;
public
static
final
String
AUDIO_MSGSM
=
BASE_TYPE_AUDIO
+
"/gsm"
;
public
static
final
String
AUDIO_OGG
=
BASE_TYPE_AUDIO
+
"/ogg"
;
public
static
final
String
AUDIO_WAV
=
BASE_TYPE_AUDIO
+
"/wav"
;
public
static
final
String
AUDIO_UNKNOWN
=
BASE_TYPE_AUDIO
+
"/x-unknown"
;
public
static
final
String
TEXT_VTT
=
BASE_TYPE_TEXT
+
"/vtt"
;
...
...
@@ -503,6 +506,26 @@ public final class MimeTypes {
}
/**
* Normalizes the MIME type provided so that equivalent MIME types are uniquely represented.
*
* @param mimeType The MIME type to normalize. The MIME type provided is returned if its
* normalized form is unknown.
* @return The normalized MIME type.
*/
public
static
String
normalizeMimeType
(
String
mimeType
)
{
switch
(
mimeType
)
{
case
BASE_TYPE_AUDIO
+
"/x-flac"
:
return
AUDIO_FLAC
;
case
BASE_TYPE_AUDIO
+
"/mp3"
:
return
AUDIO_MPEG
;
case
BASE_TYPE_AUDIO
+
"/x-wav"
:
return
AUDIO_WAV
;
default
:
return
mimeType
;
}
}
/**
* Returns the top-level type of {@code mimeType}, or null if {@code mimeType} is null or does not
* contain a forward slash character ({@code '/'}).
*/
...
...
library/common/src/main/java/com/google/android/exoplayer2/util/Util.java
View file @
b1e56304
...
...
@@ -1676,6 +1676,7 @@ public final class Util {
* @param mimeType If not null, used to infer the type.
* @return The content type.
*/
@C
.
ContentType
public
static
int
inferContentTypeWithMimeType
(
Uri
uri
,
@Nullable
String
mimeType
)
{
if
(
mimeType
==
null
)
{
return
Util
.
inferContentType
(
uri
);
...
...
library/common/src/test/java/com/google/android/exoplayer2/util/FileTypesTest.java
View file @
b1e56304
...
...
@@ -15,11 +15,17 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
util
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
getFormatFromExtension
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
HEADER_CONTENT_TYPE
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
inferFileTypeFromMimeType
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
inferFileTypeFromUri
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
android.net.Uri
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
...
...
@@ -28,30 +34,64 @@ import org.junit.runner.RunWith;
public
class
FileTypesTest
{
@Test
public
void
getFormatFromExtension_withExtension_returnsExpectedFormat
()
{
assertThat
(
getFormatFromExtension
(
Uri
.
parse
(
"filename.mp3"
))).
isEqualTo
(
FileTypes
.
MP3
);
public
void
inferFileFormat_fromResponseHeaders_returnsExpectedFormat
()
{
Map
<
String
,
List
<
String
>>
responseHeaders
=
new
HashMap
<>();
responseHeaders
.
put
(
HEADER_CONTENT_TYPE
,
Collections
.
singletonList
(
MimeTypes
.
VIDEO_MP4
));
assertThat
(
FileTypes
.
inferFileTypeFromResponseHeaders
(
responseHeaders
))
.
isEqualTo
(
FileTypes
.
MP4
);
}
@Test
public
void
inferFileFormat_fromResponseHeadersWithUnknownContentType_returnsUnknownFormat
()
{
Map
<
String
,
List
<
String
>>
responseHeaders
=
new
HashMap
<>();
responseHeaders
.
put
(
HEADER_CONTENT_TYPE
,
Collections
.
singletonList
(
"unknown"
));
assertThat
(
FileTypes
.
inferFileTypeFromResponseHeaders
(
responseHeaders
))
.
isEqualTo
(
FileTypes
.
UNKNOWN
);
}
@Test
public
void
getFormatFromExtension_withExtensionPrefix_returnsExpectedFormat
()
{
assertThat
(
getFormatFromExtension
(
Uri
.
parse
(
"filename.mka"
))).
isEqualTo
(
FileTypes
.
MATROSKA
);
public
void
inferFileFormat_fromResponseHeadersWithoutContentType_returnsUnknownFormat
()
{
assertThat
(
FileTypes
.
inferFileTypeFromResponseHeaders
(
new
HashMap
<>()))
.
isEqualTo
(
FileTypes
.
UNKNOWN
);
}
@Test
public
void
getFormatFromExtension_withUnknownExtension_returnsUnknown
Format
()
{
assertThat
(
getFormatFromExtension
(
Uri
.
parse
(
"filename.unknown"
))).
isEqualTo
(
FileTypes
.
UNKNOWN
);
public
void
inferFileFormat_fromMimeType_returnsExpected
Format
()
{
assertThat
(
FileTypes
.
inferFileTypeFromMimeType
(
"audio/x-flac"
)).
isEqualTo
(
FileTypes
.
FLAC
);
}
@Test
public
void
getFormatFromExtension_withUriNotEndingWithFilename_returnsExpectedFormat
()
{
public
void
inferFileFormat_fromUnknownMimeType_returnsUnknownFormat
()
{
assertThat
(
inferFileTypeFromMimeType
(
/* mimeType= */
"unknown"
)).
isEqualTo
(
FileTypes
.
UNKNOWN
);
}
@Test
public
void
inferFileFormat_fromNullMimeType_returnsUnknownFormat
()
{
assertThat
(
inferFileTypeFromMimeType
(
/* mimeType= */
null
)).
isEqualTo
(
FileTypes
.
UNKNOWN
);
}
@Test
public
void
inferFileFormat_fromUri_returnsExpectedFormat
()
{
assertThat
(
getFormatFromExtension
(
inferFileTypeFromUri
(
Uri
.
parse
(
"http://www.example.com/filename.mp3?query=myquery#fragment"
)))
.
isEqualTo
(
FileTypes
.
MP3
);
}
@Test
public
void
getFormatFromExtension_withNullFilename_returnsUnknownFormat
()
{
assertThat
(
getFormatFromExtension
(
Uri
.
EMPTY
)).
isEqualTo
(
FileTypes
.
UNKNOWN
);
public
void
inferFileFormat_fromUriWithExtensionPrefix_returnsExpectedFormat
()
{
assertThat
(
inferFileTypeFromUri
(
Uri
.
parse
(
"filename.mka"
))).
isEqualTo
(
FileTypes
.
MATROSKA
);
}
@Test
public
void
inferFileFormat_fromUriWithUnknownExtension_returnsUnknownFormat
()
{
assertThat
(
inferFileTypeFromUri
(
Uri
.
parse
(
"filename.unknown"
))).
isEqualTo
(
FileTypes
.
UNKNOWN
);
}
@Test
public
void
inferFileFormat_fromEmptyUri_returnsUnknownFormat
()
{
assertThat
(
inferFileTypeFromUri
(
Uri
.
EMPTY
)).
isEqualTo
(
FileTypes
.
UNKNOWN
);
}
}
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java
View file @
b1e56304
...
...
@@ -15,7 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
extractor
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
getFormatFromExtension
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
inferFileTypeFromUri
;
import
android.net.Uri
;
import
androidx.annotation.Nullable
;
...
...
@@ -272,11 +272,11 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
public
synchronized
Extractor
[]
createExtractors
(
Uri
uri
)
{
List
<
Extractor
>
extractors
=
new
ArrayList
<>(
/* initialCapacity= */
14
);
@FileTypes
.
Type
int
extensionFormat
=
getFormatFromExtension
(
uri
);
addExtractorsForFormat
(
extensionFormat
,
extractors
);
@FileTypes
.
Type
int
inferredFileType
=
inferFileTypeFromUri
(
uri
);
addExtractorsForFormat
(
inferredFileType
,
extractors
);
for
(
int
format
:
DEFAULT_EXTRACTOR_ORDER
)
{
if
(
format
!=
extensionFormat
)
{
if
(
format
!=
inferredFileType
)
{
addExtractorsForFormat
(
format
,
extractors
);
}
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/DefaultHlsExtractorFactory.java
View file @
b1e56304
...
...
@@ -15,7 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
getFormatFromExtension
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
FileTypes
.
inferFileTypeFromUri
;
import
android.net.Uri
;
import
android.text.TextUtils
;
...
...
@@ -101,12 +101,12 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
// Try selecting the extractor by the file extension.
@Nullable
Extractor
extractorByFileExtension
=
createExtractorByFileExtension
(
uri
,
format
,
muxedCaptionFormats
,
timestampAdjuster
);
Extractor
inferredExtractor
=
createInferredExtractor
(
uri
,
format
,
muxedCaptionFormats
,
timestampAdjuster
,
responseHeaders
);
extractorInput
.
resetPeekPosition
();
if
(
extractorByFileExtension
!=
null
&&
sniffQuietly
(
extractorByFileExtension
,
extractorInput
))
{
return
buildResult
(
extractorByFileExtension
);
if
(
inferredExtractor
!=
null
&&
sniffQuietly
(
inferredExtractor
,
extractorInput
))
{
return
buildResult
(
inferredExtractor
);
}
// We need to manually sniff each known type, without retrying the one selected by file
...
...
@@ -114,9 +114,9 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
// https://docs.google.com/document/d/1w2mKaWMxfz2Ei8-LdxqbPs1VLe_oudB-eryXXw9OvQQ.
// Extractor to be used if the type is not recognized.
@Nullable
Extractor
fallBackExtractor
=
extractorByFileExtension
;
@Nullable
Extractor
fallBackExtractor
=
inferredExtractor
;
if
(!(
extractorByFileExtension
instanceof
FragmentedMp4Extractor
))
{
if
(!(
inferredExtractor
instanceof
FragmentedMp4Extractor
))
{
FragmentedMp4Extractor
fragmentedMp4Extractor
=
createFragmentedMp4Extractor
(
timestampAdjuster
,
format
,
muxedCaptionFormats
);
if
(
sniffQuietly
(
fragmentedMp4Extractor
,
extractorInput
))
{
...
...
@@ -124,14 +124,14 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
}
}
if
(!(
extractorByFileExtension
instanceof
WebvttExtractor
))
{
if
(!(
inferredExtractor
instanceof
WebvttExtractor
))
{
WebvttExtractor
webvttExtractor
=
new
WebvttExtractor
(
format
.
language
,
timestampAdjuster
);
if
(
sniffQuietly
(
webvttExtractor
,
extractorInput
))
{
return
buildResult
(
webvttExtractor
);
}
}
if
(!(
extractorByFileExtension
instanceof
TsExtractor
))
{
if
(!(
inferredExtractor
instanceof
TsExtractor
))
{
TsExtractor
tsExtractor
=
createTsExtractor
(
payloadReaderFactoryFlags
,
...
...
@@ -147,28 +147,28 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
}
}
if
(!(
extractorByFileExtension
instanceof
AdtsExtractor
))
{
if
(!(
inferredExtractor
instanceof
AdtsExtractor
))
{
AdtsExtractor
adtsExtractor
=
new
AdtsExtractor
();
if
(
sniffQuietly
(
adtsExtractor
,
extractorInput
))
{
return
buildResult
(
adtsExtractor
);
}
}
if
(!(
extractorByFileExtension
instanceof
Ac3Extractor
))
{
if
(!(
inferredExtractor
instanceof
Ac3Extractor
))
{
Ac3Extractor
ac3Extractor
=
new
Ac3Extractor
();
if
(
sniffQuietly
(
ac3Extractor
,
extractorInput
))
{
return
buildResult
(
ac3Extractor
);
}
}
if
(!(
extractorByFileExtension
instanceof
Ac4Extractor
))
{
if
(!(
inferredExtractor
instanceof
Ac4Extractor
))
{
Ac4Extractor
ac4Extractor
=
new
Ac4Extractor
();
if
(
sniffQuietly
(
ac4Extractor
,
extractorInput
))
{
return
buildResult
(
ac4Extractor
);
}
}
if
(!(
extractorByFileExtension
instanceof
Mp3Extractor
))
{
if
(!(
inferredExtractor
instanceof
Mp3Extractor
))
{
Mp3Extractor
mp3Extractor
=
new
Mp3Extractor
(
/* flags= */
0
,
/* forcedFirstSampleTimestampUs= */
0
);
if
(
sniffQuietly
(
mp3Extractor
,
extractorInput
))
{
...
...
@@ -180,16 +180,20 @@ public final class DefaultHlsExtractorFactory implements HlsExtractorFactory {
}
@Nullable
private
Extractor
create
ExtractorByFileExtension
(
private
Extractor
create
InferredExtractor
(
Uri
uri
,
Format
format
,
@Nullable
List
<
Format
>
muxedCaptionFormats
,
TimestampAdjuster
timestampAdjuster
)
{
if
(
MimeTypes
.
TEXT_VTT
.
equals
(
format
.
sampleMimeType
))
{
return
new
WebvttExtractor
(
format
.
language
,
timestampAdjuster
);
TimestampAdjuster
timestampAdjuster
,
Map
<
String
,
List
<
String
>>
responseHeaders
)
{
@FileTypes
.
Type
int
fileType
=
FileTypes
.
inferFileTypeFromMimeType
(
format
.
sampleMimeType
);
if
(
fileType
==
FileTypes
.
UNKNOWN
)
{
fileType
=
FileTypes
.
inferFileTypeFromResponseHeaders
(
responseHeaders
);
}
if
(
fileType
==
FileTypes
.
UNKNOWN
)
{
fileType
=
inferFileTypeFromUri
(
uri
);
}
@FileTypes
.
Type
int
fileFormat
=
getFormatFromExtension
(
uri
);
switch
(
fileFormat
)
{
switch
(
fileType
)
{
case
FileTypes
.
WEBVTT
:
return
new
WebvttExtractor
(
format
.
language
,
timestampAdjuster
);
case
FileTypes
.
ADTS
:
...
...
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