Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
videolan
vlc
Commits
146d47c4
Commit
146d47c4
authored
Nov 26, 2015
by
Francois Cartegnie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
demux: adaptative: split update steps and handle failures
And use targetduration hint for scheduling HLS updates
parent
1a4b8ae2
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
170 additions
and
96 deletions
+170
-96
modules/demux/adaptative/PlaylistManager.cpp
modules/demux/adaptative/PlaylistManager.cpp
+18
-1
modules/demux/adaptative/PlaylistManager.h
modules/demux/adaptative/PlaylistManager.h
+3
-0
modules/demux/adaptative/SegmentTracker.cpp
modules/demux/adaptative/SegmentTracker.cpp
+12
-4
modules/demux/adaptative/playlist/BaseRepresentation.cpp
modules/demux/adaptative/playlist/BaseRepresentation.cpp
+11
-0
modules/demux/adaptative/playlist/BaseRepresentation.h
modules/demux/adaptative/playlist/BaseRepresentation.h
+3
-1
modules/demux/adaptative/playlist/SegmentInformation.cpp
modules/demux/adaptative/playlist/SegmentInformation.cpp
+0
-5
modules/demux/adaptative/playlist/SegmentInformation.hpp
modules/demux/adaptative/playlist/SegmentInformation.hpp
+0
-1
modules/demux/dash/DASHManager.cpp
modules/demux/dash/DASHManager.cpp
+33
-27
modules/demux/dash/DASHManager.h
modules/demux/dash/DASHManager.h
+3
-1
modules/demux/hls/playlist/Parser.cpp
modules/demux/hls/playlist/Parser.cpp
+4
-0
modules/demux/hls/playlist/Representation.cpp
modules/demux/hls/playlist/Representation.cpp
+40
-25
modules/demux/hls/playlist/Representation.hpp
modules/demux/hls/playlist/Representation.hpp
+4
-2
modules/demux/smooth/SmoothManager.cpp
modules/demux/smooth/SmoothManager.cpp
+35
-28
modules/demux/smooth/SmoothManager.hpp
modules/demux/smooth/SmoothManager.hpp
+4
-1
No files found.
modules/demux/adaptative/PlaylistManager.cpp
View file @
146d47c4
...
...
@@ -57,6 +57,7 @@ PlaylistManager::PlaylistManager( demux_t *p_demux_,
i_nzpcr
(
0
)
{
currentPeriod
=
playlist
->
getFirstPeriod
();
failedupdates
=
0
;
}
PlaylistManager
::~
PlaylistManager
()
...
...
@@ -248,6 +249,11 @@ bool PlaylistManager::setPosition(mtime_t time)
return
ret
;
}
bool
PlaylistManager
::
needsUpdate
()
const
{
return
playlist
->
isLive
()
&&
(
failedupdates
<
3
);
}
bool
PlaylistManager
::
seekAble
()
const
{
if
(
playlist
->
isLive
())
...
...
@@ -262,6 +268,11 @@ bool PlaylistManager::seekAble() const
return
true
;
}
void
PlaylistManager
::
scheduleNextUpdate
()
{
}
bool
PlaylistManager
::
updatePlaylist
()
{
std
::
vector
<
AbstractStream
*>::
const_iterator
it
;
...
...
@@ -318,7 +329,13 @@ int PlaylistManager::doDemux(int64_t increment)
break
;
}
updatePlaylist
();
if
(
needsUpdate
())
{
if
(
updatePlaylist
())
scheduleNextUpdate
();
else
failedupdates
++
;
}
return
VLC_DEMUXER_SUCCESS
;
}
...
...
modules/demux/adaptative/PlaylistManager.h
View file @
146d47c4
...
...
@@ -60,7 +60,9 @@ namespace adaptative
int
esCount
()
const
;
bool
setPosition
(
mtime_t
);
bool
seekAble
()
const
;
virtual
bool
needsUpdate
()
const
;
virtual
bool
updatePlaylist
();
virtual
void
scheduleNextUpdate
();
/* static callbacks */
static
int
control_callback
(
demux_t
*
,
int
,
va_list
);
...
...
@@ -88,6 +90,7 @@ namespace adaptative
time_t
nextPlaylistupdate
;
mtime_t
i_nzpcr
;
BasePeriod
*
currentPeriod
;
int
failedupdates
;
};
}
...
...
modules/demux/adaptative/SegmentTracker.cpp
View file @
146d47c4
...
...
@@ -119,9 +119,10 @@ SegmentChunk * SegmentTracker::getNextChunk(bool switch_allowed, HTTPConnectionM
initializing
=
true
;
}
bool
b_updated
=
false
;
/* Ensure ephemere content is updated/loaded */
if
(
rep
->
needsUpdate
())
rep
->
runLocalUpdates
(
getSegmentStart
(),
count
,
false
);
b_updated
=
rep
->
runLocalUpdates
(
getSegmentStart
(),
count
,
false
);
if
(
prevRep
&&
!
rep
->
consistentSegmentNumber
())
{
...
...
@@ -134,8 +135,12 @@ SegmentChunk * SegmentTracker::getNextChunk(bool switch_allowed, HTTPConnectionM
first
=
false
;
}
if
(
!
rep
->
consistentSegmentNumber
())
curRepresentation
->
pruneBySegmentNumber
(
count
);
if
(
b_updated
)
{
if
(
!
rep
->
consistentSegmentNumber
())
curRepresentation
->
pruneBySegmentNumber
(
count
);
curRepresentation
->
scheduleNextUpdate
(
count
);
}
if
(
!
init_sent
)
{
...
...
@@ -249,8 +254,11 @@ void SegmentTracker::pruneFromCurrent()
void
SegmentTracker
::
updateSelected
()
{
if
(
curRepresentation
)
if
(
curRepresentation
&&
curRepresentation
->
needsUpdate
())
{
curRepresentation
->
runLocalUpdates
(
getSegmentStart
(),
count
,
true
);
curRepresentation
->
scheduleNextUpdate
(
count
);
}
}
void
SegmentTracker
::
notify
(
const
SegmentTrackerEvent
&
event
)
...
...
modules/demux/adaptative/playlist/BaseRepresentation.cpp
View file @
146d47c4
...
...
@@ -30,6 +30,7 @@
#include "BaseRepresentation.h"
#include "BaseAdaptationSet.h"
#include "SegmentTemplate.h"
#include "SegmentTimeline.h"
#include "ID.hpp"
using
namespace
adaptative
;
...
...
@@ -77,6 +78,16 @@ bool BaseRepresentation::needsUpdate() const
return
false
;
}
bool
BaseRepresentation
::
runLocalUpdates
(
mtime_t
,
uint64_t
,
bool
)
{
return
false
;
}
void
BaseRepresentation
::
scheduleNextUpdate
(
uint64_t
)
{
}
bool
BaseRepresentation
::
consistentSegmentNumber
()
const
{
return
b_consistent
;
...
...
modules/demux/adaptative/playlist/BaseRepresentation.h
View file @
146d47c4
...
...
@@ -58,10 +58,12 @@ namespace adaptative
void
setBandwidth
(
uint64_t
bandwidth
);
const
std
::
list
<
std
::
string
>
&
getCodecs
()
const
;
void
addCodec
(
const
std
::
string
&
);
virtual
bool
needsUpdate
()
const
;
bool
consistentSegmentNumber
()
const
;
virtual
mtime_t
getMinAheadTime
(
uint64_t
)
const
;
virtual
bool
needsUpdate
()
const
;
virtual
bool
runLocalUpdates
(
mtime_t
,
uint64_t
,
bool
);
virtual
void
scheduleNextUpdate
(
uint64_t
);
virtual
void
debug
(
vlc_object_t
*
,
int
=
0
)
const
;
...
...
modules/demux/adaptative/playlist/SegmentInformation.cpp
View file @
146d47c4
...
...
@@ -448,11 +448,6 @@ uint64_t SegmentInformation::translateSegmentNumber(uint64_t num, const SegmentI
return
num
;
}
void
SegmentInformation
::
runLocalUpdates
(
mtime_t
,
uint64_t
,
bool
)
{
}
SegmentInformation
::
SwitchPolicy
SegmentInformation
::
getSwitchPolicy
()
const
{
if
(
switchpolicy
==
SWITCH_UNKNOWN
)
...
...
modules/demux/adaptative/playlist/SegmentInformation.hpp
View file @
146d47c4
...
...
@@ -88,7 +88,6 @@ namespace adaptative
virtual
void
mergeWithTimeline
(
SegmentTimeline
*
);
/* ! don't use with global merge */
virtual
void
pruneBySegmentNumber
(
uint64_t
);
virtual
uint64_t
translateSegmentNumber
(
uint64_t
,
const
SegmentInformation
*
)
const
;
virtual
void
runLocalUpdates
(
mtime_t
,
uint64_t
,
bool
);
protected:
std
::
size_t
getAllSegments
(
std
::
vector
<
ISegment
*>
&
)
const
;
...
...
modules/demux/dash/DASHManager.cpp
View file @
146d47c4
...
...
@@ -59,15 +59,42 @@ DASHManager::~DASHManager ()
{
}
bool
DASHManager
::
updatePlaylist
()
void
DASHManager
::
scheduleNextUpdate
()
{
if
(
!
playlist
->
isLive
()
||
!
playlist
->
minUpdatePeriod
.
Get
())
return
true
;
time_t
now
=
time
(
NULL
);
if
(
nextPlaylistupdate
&&
now
<
nextPlaylistupdate
)
return
true
;
mtime_t
minbuffer
=
0
;
std
::
vector
<
AbstractStream
*>::
const_iterator
it
;
for
(
it
=
streams
.
begin
();
it
!=
streams
.
end
();
++
it
)
{
const
AbstractStream
*
st
=
*
it
;
const
mtime_t
m
=
st
->
getMinAheadTime
();
if
(
m
>
0
&&
(
m
<
minbuffer
||
minbuffer
==
0
))
minbuffer
=
m
;
}
minbuffer
/=
2
;
if
(
playlist
->
minUpdatePeriod
.
Get
()
>
minbuffer
)
minbuffer
=
playlist
->
minUpdatePeriod
.
Get
();
if
(
minbuffer
<
5
*
CLOCK_FREQ
)
minbuffer
=
5
*
CLOCK_FREQ
;
nextPlaylistupdate
=
now
+
minbuffer
/
CLOCK_FREQ
;
msg_Dbg
(
p_demux
,
"Updated MPD, next update in %"
PRId64
"s"
,
(
mtime_t
)
nextPlaylistupdate
-
now
);
}
bool
DASHManager
::
needsUpdate
()
const
{
if
(
nextPlaylistupdate
&&
time
(
NULL
)
<
nextPlaylistupdate
)
return
false
;
return
PlaylistManager
::
needsUpdate
();
}
bool
DASHManager
::
updatePlaylist
()
{
/* do update */
if
(
nextPlaylistupdate
)
{
...
...
@@ -83,7 +110,6 @@ bool DASHManager::updatePlaylist()
if
(
!
mpdstream
)
{
block_Release
(
p_block
);
nextPlaylistupdate
=
now
+
playlist
->
minUpdatePeriod
.
Get
()
/
CLOCK_FREQ
;
return
false
;
}
...
...
@@ -92,7 +118,6 @@ bool DASHManager::updatePlaylist()
{
stream_Delete
(
mpdstream
);
block_Release
(
p_block
);
nextPlaylistupdate
=
now
+
playlist
->
minUpdatePeriod
.
Get
()
/
CLOCK_FREQ
;
return
false
;
}
...
...
@@ -117,25 +142,6 @@ bool DASHManager::updatePlaylist()
block_Release
(
p_block
);
}
/* Compute new MPD update time */
mtime_t
mininterval
=
0
;
mtime_t
maxinterval
=
0
;
playlist
->
getPlaylistDurationsRange
(
&
mininterval
,
&
maxinterval
);
if
(
playlist
->
minUpdatePeriod
.
Get
()
>
mininterval
)
mininterval
=
playlist
->
minUpdatePeriod
.
Get
();
if
(
mininterval
<
5
*
CLOCK_FREQ
)
mininterval
=
5
*
CLOCK_FREQ
;
if
(
maxinterval
<
mininterval
)
maxinterval
=
mininterval
;
nextPlaylistupdate
=
now
+
(
mininterval
+
(
maxinterval
-
mininterval
)
/
2
)
/
CLOCK_FREQ
;
msg_Dbg
(
p_demux
,
"Updated MPD, next update in %"
PRId64
"s (%"
PRId64
"..%"
PRId64
")"
,
(
mtime_t
)
nextPlaylistupdate
-
now
,
mininterval
/
CLOCK_FREQ
,
maxinterval
/
CLOCK_FREQ
);
return
true
;
}
...
...
modules/demux/dash/DASHManager.h
View file @
146d47c4
...
...
@@ -49,7 +49,9 @@ namespace dash
logic
::
AbstractAdaptationLogic
::
LogicType
type
);
virtual
~
DASHManager
();
virtual
bool
updatePlaylist
();
//reimpl
virtual
bool
needsUpdate
()
const
;
/* reimpl */
virtual
bool
updatePlaylist
();
/* reimpl */
virtual
void
scheduleNextUpdate
();
/* reimpl */
static
bool
isDASH
(
xml
::
Node
*
);
static
bool
mimeMatched
(
const
std
::
string
&
);
...
...
modules/demux/hls/playlist/Parser.cpp
View file @
146d47c4
...
...
@@ -302,6 +302,10 @@ void M3U8Parser::parseSegments(vlc_object_t *p_obj, Representation *rep, const s
}
break
;
case
SingleValueTag
:
:
EXTXTARGETDURATION
:
rep
->
targetDuration
=
static_cast
<
const
SingleValueTag
*>
(
tag
)
->
getValue
().
decimal
();
break
;
case
SingleValueTag
:
:
EXTXPLAYLISTTYPE
:
rep
->
b_live
=
(
static_cast
<
const
SingleValueTag
*>
(
tag
)
->
getValue
().
value
!=
"VOD"
);
break
;
...
...
modules/demux/hls/playlist/Representation.cpp
View file @
146d47c4
...
...
@@ -42,7 +42,8 @@ Representation::Representation ( BaseAdaptationSet *set ) :
b_live
=
true
;
b_loaded
=
false
;
switchpolicy
=
SegmentInformation
::
SWITCH_SEGMENT_ALIGNED
;
/* FIXME: based on streamformat */
nextPlaylistupdate
=
0
;
nextUpdateTime
=
0
;
targetDuration
=
0
;
streamFormat
=
StreamFormat
::
UNKNOWN
;
}
...
...
@@ -97,16 +98,48 @@ void Representation::debug(vlc_object_t *obj, int indent) const
}
}
void
Representation
::
scheduleNextUpdate
(
uint64_t
number
)
{
const
AbstractPlaylist
*
playlist
=
getPlaylist
();
const
time_t
now
=
time
(
NULL
);
/* Compute new update time */
mtime_t
minbuffer
=
getMinAheadTime
(
number
);
/* Update frequency must always be at least targetDuration (if any)*/
if
(
targetDuration
)
{
if
(
minbuffer
>=
3
*
CLOCK_FREQ
*
targetDuration
)
minbuffer
-=
2
*
CLOCK_FREQ
*
targetDuration
;
}
else
{
minbuffer
/=
2
;
if
(
targetDuration
>
minbuffer
/
CLOCK_FREQ
)
minbuffer
=
targetDuration
*
CLOCK_FREQ
;
}
if
(
minbuffer
<
CLOCK_FREQ
)
minbuffer
=
CLOCK_FREQ
;
nextUpdateTime
=
now
+
minbuffer
/
CLOCK_FREQ
;
msg_Dbg
(
playlist
->
getVLCObject
(),
"Updated playlist ID %s, next update in %"
PRId64
"s"
,
getID
().
str
().
c_str
(),
(
mtime_t
)
nextUpdateTime
-
now
);
debug
(
playlist
->
getVLCObject
(),
0
);
}
bool
Representation
::
needsUpdate
()
const
{
return
true
;
return
!
b_loaded
||
(
isLive
()
&&
nextUpdateTime
<
time
(
NULL
))
;
}
void
Representation
::
runLocalUpdates
(
mtime_t
,
uint64_t
number
,
bool
prune
)
bool
Representation
::
runLocalUpdates
(
mtime_t
,
uint64_t
number
,
bool
prune
)
{
const
time_t
now
=
time
(
NULL
);
const
AbstractPlaylist
*
playlist
=
getPlaylist
();
if
(
!
b_loaded
||
(
isLive
()
&&
next
Playlistupdat
e
<
now
))
if
(
!
b_loaded
||
(
isLive
()
&&
next
UpdateTim
e
<
now
))
{
M3U8Parser
parser
;
parser
.
appendSegmentsFromPlaylistURI
(
playlist
->
getVLCObject
(),
this
);
...
...
@@ -115,28 +148,10 @@ void Representation::runLocalUpdates(mtime_t, uint64_t number, bool prune)
if
(
prune
)
pruneBySegmentNumber
(
number
);
/* Compute new update time */
mtime_t
mininterval
=
0
;
mtime_t
maxinterval
=
0
;
getDurationsRange
(
&
mininterval
,
&
maxinterval
);
if
(
playlist
->
minUpdatePeriod
.
Get
()
>
mininterval
)
mininterval
=
playlist
->
minUpdatePeriod
.
Get
();
if
(
mininterval
<
5
*
CLOCK_FREQ
)
mininterval
=
5
*
CLOCK_FREQ
;
if
(
maxinterval
<
mininterval
)
maxinterval
=
mininterval
;
nextPlaylistupdate
=
now
+
(
mininterval
+
(
maxinterval
-
mininterval
)
/
2
)
/
CLOCK_FREQ
;
msg_Dbg
(
playlist
->
getVLCObject
(),
"Updated playlist ID %s, next update in %"
PRId64
"s"
,
getID
().
str
().
c_str
(),
(
mtime_t
)
nextPlaylistupdate
-
now
);
debug
(
playlist
->
getVLCObject
(),
0
);
return
true
;
}
return
true
;
}
void
Representation
::
getDurationsRange
(
mtime_t
*
min
,
mtime_t
*
max
)
const
...
...
modules/demux/hls/playlist/Representation.hpp
View file @
146d47c4
...
...
@@ -47,9 +47,10 @@ namespace hls
Url
getPlaylistUrl
()
const
;
bool
isLive
()
const
;
bool
initialized
()
const
;
virtual
void
scheduleNextUpdate
(
uint64_t
);
/* reimpl */
virtual
bool
needsUpdate
()
const
;
/* reimpl */
virtual
void
debug
(
vlc_object_t
*
,
int
)
const
;
/* reimpl */
virtual
void
runLocalUpdates
(
mtime_t
,
uint64_t
,
bool
);
/* reimpl */
virtual
bool
runLocalUpdates
(
mtime_t
,
uint64_t
,
bool
);
/* reimpl */
virtual
void
getDurationsRange
(
mtime_t
*
,
mtime_t
*
)
const
;
/* reimpl */
virtual
uint64_t
translateSegmentNumber
(
uint64_t
,
const
SegmentInformation
*
)
const
;
/* reimpl */
...
...
@@ -57,7 +58,8 @@ namespace hls
StreamFormat
streamFormat
;
bool
b_live
;
bool
b_loaded
;
time_t
nextPlaylistupdate
;
time_t
nextUpdateTime
;
time_t
targetDuration
;
Url
playlistUrl
;
Property
<
std
::
string
>
audio
;
Property
<
std
::
string
>
video
;
...
...
modules/demux/smooth/SmoothManager.cpp
View file @
146d47c4
...
...
@@ -92,19 +92,44 @@ bool SmoothManager::updatePlaylist()
return
updatePlaylist
(
false
);
}
bool
SmoothManager
::
updatePlaylist
(
bool
forcemanifest
)
void
SmoothManager
::
scheduleNextUpdate
(
)
{
/* FIXME: do update from manifest after resuming from pause */
if
(
!
playlist
->
isLive
()
||
!
playlist
->
minUpdatePeriod
.
Get
())
return
true
;
time_t
now
=
time
(
NULL
);
if
(
nextPlaylistupdate
&&
now
<
nextPlaylistupdate
)
return
true
;
mtime_t
minbuffer
=
0
;
std
::
vector
<
AbstractStream
*>::
const_iterator
it
;
for
(
it
=
streams
.
begin
();
it
!=
streams
.
end
();
++
it
)
{
const
AbstractStream
*
st
=
*
it
;
const
mtime_t
m
=
st
->
getMinAheadTime
();
if
(
m
>
0
&&
(
m
<
minbuffer
||
minbuffer
==
0
))
minbuffer
=
m
;
}
minbuffer
/=
2
;
if
(
playlist
->
minUpdatePeriod
.
Get
()
>
minbuffer
)
minbuffer
=
playlist
->
minUpdatePeriod
.
Get
();
if
(
minbuffer
<
5
*
CLOCK_FREQ
)
minbuffer
=
5
*
CLOCK_FREQ
;
nextPlaylistupdate
=
now
+
minbuffer
/
CLOCK_FREQ
;
msg_Dbg
(
p_demux
,
"Updated playlist, next update in %"
PRId64
"s"
,
(
mtime_t
)
nextPlaylistupdate
-
now
);
}
bool
SmoothManager
::
needsUpdate
()
const
{
if
(
nextPlaylistupdate
&&
time
(
NULL
)
<
nextPlaylistupdate
)
return
false
;
return
PlaylistManager
::
needsUpdate
();
}
mtime_t
mininterval
=
0
;
mtime_t
maxinterval
=
0
;
bool
SmoothManager
::
updatePlaylist
(
bool
forcemanifest
)
{
/* FIXME: do update from manifest after resuming from pause */
std
::
vector
<
AbstractStream
*>::
iterator
it
;
for
(
it
=
streams
.
begin
();
it
!=
streams
.
end
();
it
++
)
...
...
@@ -118,7 +143,6 @@ bool SmoothManager::updatePlaylist(bool forcemanifest)
Manifest
*
newManifest
=
fetchManifest
();
if
(
newManifest
)
{
newManifest
->
getPlaylistDurationsRange
(
&
mininterval
,
&
maxinterval
);
playlist
->
mergeWith
(
newManifest
,
0
);
delete
newManifest
;
...
...
@@ -129,26 +153,9 @@ bool SmoothManager::updatePlaylist(bool forcemanifest)
playlist
->
debug
();
#endif
}
else
return
false
;
}
/* Compute new Manifest update time */
if
(
!
mininterval
&&
!
maxinterval
)
playlist
->
getPlaylistDurationsRange
(
&
mininterval
,
&
maxinterval
);
if
(
playlist
->
minUpdatePeriod
.
Get
()
>
mininterval
)
mininterval
=
playlist
->
minUpdatePeriod
.
Get
();
if
(
mininterval
<
5
*
CLOCK_FREQ
)
mininterval
=
5
*
CLOCK_FREQ
;
if
(
maxinterval
<
mininterval
)
maxinterval
=
mininterval
;
nextPlaylistupdate
=
now
+
(
mininterval
+
(
maxinterval
-
mininterval
)
/
2
)
/
CLOCK_FREQ
;
// msg_Dbg(p_demux, "Updated Manifest, next update in %" PRId64 "s (%" PRId64 "..%" PRId64 ")",
// (mtime_t) nextPlaylistupdate - now, mininterval/ CLOCK_FREQ, maxinterval/ CLOCK_FREQ );
return
true
;
}
...
...
modules/demux/smooth/SmoothManager.hpp
View file @
146d47c4
...
...
@@ -44,7 +44,10 @@ namespace smooth
logic
::
AbstractAdaptationLogic
::
LogicType
type
);
virtual
~
SmoothManager
();
virtual
bool
updatePlaylist
();
//reimpl
virtual
bool
needsUpdate
()
const
;
/* reimpl */
virtual
void
scheduleNextUpdate
();
/* reimpl */
virtual
bool
updatePlaylist
();
/* reimpl */
static
bool
isSmoothStreaming
(
xml
::
Node
*
);
static
bool
mimeMatched
(
const
std
::
string
&
);
...
...
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