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
d9afe510
authored
Jan 18, 2022
by
Dustin
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Refactor, remove dead code
parent
8d90498f
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
37 additions
and
173 deletions
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviHeaderBox.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviUtil.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/Box.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ListBox.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ResidentBox.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBox.java
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java
View file @
d9afe510
...
@@ -26,8 +26,23 @@ import java.util.Map;
...
@@ -26,8 +26,23 @@ import java.util.Map;
* https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference
* https://docs.microsoft.com/en-us/windows/win32/directshow/avi-riff-file-reference
*/
*/
public
class
AviExtractor
implements
Extractor
{
public
class
AviExtractor
implements
Extractor
{
static
final
long
UINT_MASK
=
0xffffffff
L
;
static
long
getUInt
(
ByteBuffer
byteBuffer
)
{
return
byteBuffer
.
getInt
()
&
UINT_MASK
;
}
@NonNull
static
String
toString
(
int
tag
)
{
final
StringBuilder
sb
=
new
StringBuilder
(
4
);
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
sb
.
append
((
char
)(
tag
&
0xff
));
tag
>>=
8
;
}
return
sb
.
toString
();
}
static
final
String
TAG
=
"AviExtractor"
;
static
final
String
TAG
=
"AviExtractor"
;
static
final
int
KEY_FRAME_MASK
=
Integer
.
MIN_VALUE
;
private
static
final
int
PEEK_BYTES
=
28
;
private
static
final
int
PEEK_BYTES
=
28
;
private
static
final
int
STATE_READ_TRACKS
=
0
;
private
static
final
int
STATE_READ_TRACKS
=
0
;
...
@@ -39,8 +54,8 @@ public class AviExtractor implements Extractor {
...
@@ -39,8 +54,8 @@ public class AviExtractor implements Extractor {
private
static
final
int
AVIIF_KEYFRAME
=
16
;
private
static
final
int
AVIIF_KEYFRAME
=
16
;
static
final
int
RIFF
=
AviUtil
.
toInt
(
new
byte
[]{
'R'
,
'I'
,
'F'
,
'F'
}
);
static
final
int
RIFF
=
'R'
|
(
'I'
<<
8
)
|
(
'F'
<<
16
)
|
(
'F'
<<
24
);
static
final
int
AVI_
=
AviUtil
.
toInt
(
new
byte
[]{
'A'
,
'V'
,
'I'
,
' '
}
);
static
final
int
AVI_
=
'A'
|
(
'V'
<<
8
)
|
(
'I'
<<
16
)
|
(
' '
<<
24
);
//Stream List
//Stream List
static
final
int
STRL
=
's'
|
(
't'
<<
8
)
|
(
'r'
<<
16
)
|
(
'l'
<<
24
);
static
final
int
STRL
=
's'
|
(
't'
<<
8
)
|
(
'r'
<<
16
)
|
(
'l'
<<
24
);
//movie data box
//movie data box
...
@@ -103,7 +118,7 @@ public class AviExtractor implements Extractor {
...
@@ -103,7 +118,7 @@ public class AviExtractor implements Extractor {
if
(
riff
!=
AviExtractor
.
RIFF
)
{
if
(
riff
!=
AviExtractor
.
RIFF
)
{
return
false
;
return
false
;
}
}
long
reportedLen
=
AviUtil
.
getUInt
(
byteBuffer
)
+
byteBuffer
.
position
();
long
reportedLen
=
getUInt
(
byteBuffer
)
+
byteBuffer
.
position
();
final
long
inputLen
=
input
.
getLength
();
final
long
inputLen
=
input
.
getLength
();
if
(
inputLen
!=
C
.
LENGTH_UNSET
&&
inputLen
!=
reportedLen
)
{
if
(
inputLen
!=
C
.
LENGTH_UNSET
&&
inputLen
!=
reportedLen
)
{
Log
.
w
(
TAG
,
"Header length doesn't match stream length"
);
Log
.
w
(
TAG
,
"Header length doesn't match stream length"
);
...
@@ -136,7 +151,7 @@ public class AviExtractor implements Extractor {
...
@@ -136,7 +151,7 @@ public class AviExtractor implements Extractor {
if
(
riff
!=
AviExtractor
.
RIFF
)
{
if
(
riff
!=
AviExtractor
.
RIFF
)
{
return
null
;
return
null
;
}
}
long
reportedLen
=
AviUtil
.
getUInt
(
byteBuffer
)
+
byteBuffer
.
position
();
long
reportedLen
=
getUInt
(
byteBuffer
)
+
byteBuffer
.
position
();
final
long
inputLen
=
input
.
getLength
();
final
long
inputLen
=
input
.
getLength
();
if
(
inputLen
!=
C
.
LENGTH_UNSET
&&
inputLen
!=
reportedLen
)
{
if
(
inputLen
!=
C
.
LENGTH_UNSET
&&
inputLen
!=
reportedLen
)
{
Log
.
w
(
TAG
,
"Header length doesn't match stream length"
);
Log
.
w
(
TAG
,
"Header length doesn't match stream length"
);
...
@@ -177,18 +192,15 @@ public class AviExtractor implements Extractor {
...
@@ -177,18 +192,15 @@ public class AviExtractor implements Extractor {
if
(
headerList
==
null
)
{
if
(
headerList
==
null
)
{
throw
new
IOException
(
"AVI Header List not found"
);
throw
new
IOException
(
"AVI Header List not found"
);
}
}
final
List
<
Box
>
headerChildren
=
headerList
.
getChildren
();
aviHeader
=
headerList
.
getChild
(
AviHeaderBox
.
class
);
aviHeader
=
AviUtil
.
getBox
(
headerChildren
,
AviHeaderBox
.
class
);
if
(
aviHeader
==
null
)
{
if
(
aviHeader
==
null
)
{
throw
new
IOException
(
"AviHeader not found"
);
throw
new
IOException
(
"AviHeader not found"
);
}
}
//This is usually wrong, so it will be overwritten by video if present
//This is usually wrong, so it will be overwritten by video if present
durationUs
=
aviHeader
.
getFrames
()
*
(
long
)
aviHeader
.
getMicroSecPerFrame
();
durationUs
=
aviHeader
.
getFrames
()
*
(
long
)
aviHeader
.
getMicroSecPerFrame
();
headerChildren
.
remove
(
aviHeader
);
//headerChildren should only be Stream Lists now
int
streamId
=
0
;
int
streamId
=
0
;
for
(
Box
box
:
header
Children
)
{
for
(
Box
box
:
header
List
.
getChildren
()
)
{
if
(
box
instanceof
ListBox
&&
((
ListBox
)
box
).
getListType
()
==
STRL
)
{
if
(
box
instanceof
ListBox
&&
((
ListBox
)
box
).
getListType
()
==
STRL
)
{
final
ListBox
streamList
=
(
ListBox
)
box
;
final
ListBox
streamList
=
(
ListBox
)
box
;
final
List
<
Box
>
streamChildren
=
streamList
.
getChildren
();
final
List
<
Box
>
streamChildren
=
streamList
.
getChildren
();
...
@@ -238,7 +250,6 @@ public class AviExtractor implements Extractor {
...
@@ -238,7 +250,6 @@ public class AviExtractor implements Extractor {
builder
.
setChannelCount
(
audioFormat
.
getChannels
());
builder
.
setChannelCount
(
audioFormat
.
getChannels
());
builder
.
setSampleRate
(
audioFormat
.
getSamplesPerSecond
());
builder
.
setSampleRate
(
audioFormat
.
getSamplesPerSecond
());
if
(
audioFormat
.
getFormatTag
()
==
AudioFormat
.
WAVE_FORMAT_PCM
)
{
if
(
audioFormat
.
getFormatTag
()
==
AudioFormat
.
WAVE_FORMAT_PCM
)
{
//TODO: Determine if this is LE or BE - Most likely LE
final
short
bps
=
audioFormat
.
getBitsPerSample
();
final
short
bps
=
audioFormat
.
getBitsPerSample
();
if
(
bps
==
8
)
{
if
(
bps
==
8
)
{
builder
.
setPcmEncoding
(
C
.
ENCODING_PCM_8BIT
);
builder
.
setPcmEncoding
(
C
.
ENCODING_PCM_8BIT
);
...
@@ -268,7 +279,7 @@ public class AviExtractor implements Extractor {
...
@@ -268,7 +279,7 @@ public class AviExtractor implements Extractor {
ByteBuffer
byteBuffer
=
allocate
(
12
);
ByteBuffer
byteBuffer
=
allocate
(
12
);
input
.
readFully
(
byteBuffer
.
array
(),
0
,
12
);
input
.
readFully
(
byteBuffer
.
array
(),
0
,
12
);
final
int
tag
=
byteBuffer
.
getInt
();
final
int
tag
=
byteBuffer
.
getInt
();
final
long
size
=
byteBuffer
.
getInt
()
&
AviUtil
.
UINT_MASK
;
final
long
size
=
getUInt
(
byteBuffer
)
;
final
long
position
=
input
.
getPosition
();
final
long
position
=
input
.
getPosition
();
//-4 because we over read for the LIST type
//-4 because we over read for the LIST type
long
nextBox
=
position
+
size
-
4
;
long
nextBox
=
position
+
size
-
4
;
...
@@ -330,7 +341,7 @@ public class AviExtractor implements Extractor {
...
@@ -330,7 +341,7 @@ public class AviExtractor implements Extractor {
final
AviTrack
aviTrack
=
idTrackMap
.
get
(
id
);
final
AviTrack
aviTrack
=
idTrackMap
.
get
(
id
);
if
(
aviTrack
==
null
)
{
if
(
aviTrack
==
null
)
{
if
(
id
!=
AviExtractor
.
REC_
)
{
if
(
id
!=
AviExtractor
.
REC_
)
{
Log
.
w
(
TAG
,
"Unknown Track Type: "
+
AviUtil
.
toString
(
id
));
Log
.
w
(
TAG
,
"Unknown Track Type: "
+
toString
(
id
));
}
}
indexByteBuffer
.
position
(
indexByteBuffer
.
position
()
+
12
);
indexByteBuffer
.
position
(
indexByteBuffer
.
position
()
+
12
);
continue
;
continue
;
...
@@ -413,7 +424,7 @@ public class AviExtractor implements Extractor {
...
@@ -413,7 +424,7 @@ public class AviExtractor implements Extractor {
}
else
{
}
else
{
seekPosition
.
position
=
input
.
getPosition
()
+
sampleSize
;
seekPosition
.
position
=
input
.
getPosition
()
+
sampleSize
;
if
(
id
!=
JUNK
)
{
if
(
id
!=
JUNK
)
{
Log
.
w
(
TAG
,
"Unknown tag="
+
AviUtil
.
toString
(
id
)
+
" pos="
+
(
input
.
getPosition
()
-
8
)
Log
.
w
(
TAG
,
"Unknown tag="
+
toString
(
id
)
+
" pos="
+
(
input
.
getPosition
()
-
8
)
+
" size="
+
sampleSize
+
" moviEnd="
+
moviEnd
);
+
" size="
+
sampleSize
+
" moviEnd="
+
moviEnd
);
}
}
}
}
...
@@ -470,7 +481,6 @@ public class AviExtractor implements Extractor {
...
@@ -470,7 +481,6 @@ public class AviExtractor implements Extractor {
@Override
@Override
public
void
seek
(
long
position
,
long
timeUs
)
{
public
void
seek
(
long
position
,
long
timeUs
)
{
Log
.
d
(
"Test"
,
"Seek: pos="
+
position
+
" us="
+
timeUs
);
if
(
position
==
0
)
{
if
(
position
==
0
)
{
if
(
moviOffset
!=
0
)
{
if
(
moviOffset
!=
0
)
{
resetFrames
();
resetFrames
();
...
...
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviHeaderBox.java
View file @
d9afe510
...
@@ -12,11 +12,6 @@ public class AviHeaderBox extends ResidentBox {
...
@@ -12,11 +12,6 @@ public class AviHeaderBox extends ResidentBox {
super
(
type
,
size
,
byteBuffer
);
super
(
type
,
size
,
byteBuffer
);
}
}
@Override
boolean
assertType
()
{
return
simpleAssert
(
AVIH
);
}
boolean
hasIndex
()
{
boolean
hasIndex
()
{
return
(
getFlags
()
&
AVIF_HASINDEX
)
>
0
;
return
(
getFlags
()
&
AVIF_HASINDEX
)
>
0
;
}
}
...
...
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviUtil.java
deleted
100644 → 0
View file @
8d90498f
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
avi
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.util.List
;
public
class
AviUtil
{
static
final
long
UINT_MASK
=
0xffffffff
L
;
static
int
toInt
(
byte
[]
bytes
)
{
int
i
=
0
;
for
(
int
b
=
bytes
.
length
-
1
;
b
>=
0
;
b
--)
{
i
<<=
8
;
i
|=
bytes
[
b
];
}
return
i
;
}
static
long
getUInt
(
ByteBuffer
byteBuffer
)
{
return
byteBuffer
.
getInt
()
&
UINT_MASK
;
}
static
void
copy
(
ByteBuffer
source
,
ByteBuffer
dest
,
int
bytes
)
{
final
int
inLimit
=
source
.
limit
();
source
.
limit
(
source
.
position
()
+
bytes
);
dest
.
put
(
source
);
source
.
limit
(
inLimit
);
}
static
ByteBuffer
getByteBuffer
(
final
ByteBuffer
source
,
final
int
size
,
final
ExtractorInput
input
)
throws
IOException
{
final
ByteBuffer
byteBuffer
=
AviExtractor
.
allocate
(
size
);
if
(
size
<
source
.
remaining
())
{
copy
(
source
,
byteBuffer
,
size
);
}
else
{
final
int
copy
=
source
.
remaining
();
copy
(
source
,
byteBuffer
,
copy
);
int
remaining
=
size
-
copy
;
final
int
offset
=
byteBuffer
.
position
()
+
byteBuffer
.
arrayOffset
();
input
.
readFully
(
byteBuffer
.
array
(),
offset
,
remaining
,
false
);
}
return
byteBuffer
;
}
@NonNull
static
String
toString
(
int
tag
)
{
final
StringBuilder
sb
=
new
StringBuilder
(
4
);
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
sb
.
append
((
char
)(
tag
&
0xff
));
tag
>>=
8
;
}
return
sb
.
toString
();
}
@Nullable
static
<
T
extends
Box
>
T
getBox
(
List
<?
extends
Box
>
list
,
Class
<
T
>
clazz
)
{
for
(
Box
box
:
list
)
{
if
(
box
.
getClass
()
==
clazz
)
{
return
(
T
)
box
;
}
}
return
null
;
}
}
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/Box.java
View file @
d9afe510
...
@@ -13,11 +13,7 @@ public class Box {
...
@@ -13,11 +13,7 @@ public class Box {
}
}
public
long
getSize
()
{
public
long
getSize
()
{
return
size
&
AviUtil
.
UINT_MASK
;
return
size
&
AviExtractor
.
UINT_MASK
;
}
public
int
getSizeInt
()
{
return
size
;
}
}
public
int
getType
()
{
public
int
getType
()
{
...
@@ -28,8 +24,4 @@ public class Box {
...
@@ -28,8 +24,4 @@ public class Box {
return
getType
()
==
expected
;
return
getType
()
==
expected
;
}
}
boolean
assertType
()
{
//Generic box, nothing to assert
return
true
;
}
}
}
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java
View file @
d9afe510
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
avi
;
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
avi
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
...
@@ -16,25 +15,6 @@ public class BoxFactory {
...
@@ -16,25 +15,6 @@ public class BoxFactory {
return
Arrays
.
binarySearch
(
types
,
type
)
<
0
;
return
Arrays
.
binarySearch
(
types
,
type
)
<
0
;
}
}
@Nullable
public
ResidentBox
createBox
(
final
int
type
,
final
int
size
,
final
ByteBuffer
byteBuffer
)
{
final
ByteBuffer
boxBuffer
=
AviExtractor
.
allocate
(
size
);
AviUtil
.
copy
(
byteBuffer
,
boxBuffer
,
size
);
//TODO: Deal with list
switch
(
type
)
{
case
AviHeaderBox
.
AVIH
:
return
new
AviHeaderBox
(
type
,
size
,
boxBuffer
);
case
StreamHeaderBox
.
STRH
:
return
new
StreamHeaderBox
(
type
,
size
,
boxBuffer
);
case
StreamFormatBox
.
STRF
:
return
new
StreamFormatBox
(
type
,
size
,
boxBuffer
);
case
StreamDataBox
.
STRD
:
return
new
StreamDataBox
(
type
,
size
,
boxBuffer
);
default
:
return
null
;
}
}
private
ResidentBox
createBoxImpl
(
final
int
type
,
final
int
size
,
final
ByteBuffer
boxBuffer
)
{
private
ResidentBox
createBoxImpl
(
final
int
type
,
final
int
size
,
final
ByteBuffer
boxBuffer
)
{
switch
(
type
)
{
switch
(
type
)
{
case
AviHeaderBox
.
AVIH
:
case
AviHeaderBox
.
AVIH
:
...
...
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ListBox.java
View file @
d9afe510
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
avi
;
package
com
.
google
.
android
.
exoplayer2
.
extractor
.
avi
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
com.google.android.exoplayer2.extractor.ExtractorInput
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
...
@@ -29,26 +30,20 @@ public class ListBox extends Box {
...
@@ -29,26 +30,20 @@ public class ListBox extends Box {
return
listType
;
return
listType
;
}
}
@Override
boolean
assertType
()
{
return
simpleAssert
(
LIST
);
}
@NonNull
@NonNull
public
List
<
Box
>
getChildren
()
{
public
List
<
Box
>
getChildren
()
{
return
new
ArrayList
<>(
children
);
return
new
ArrayList
<>(
children
);
}
}
// static List<ResidentBox> realizeChildren(final ByteBuffer byteBuffer, final BoxFactory boxFactory) {
@Nullable
// final List<ResidentBox> list = new ArrayList<>();
public
<
T
extends
Box
>
T
getChild
(
Class
<
T
>
c
)
{
// while (byteBuffer.hasRemaining()) {
for
(
Box
box
:
children
)
{
// final int type = byteBuffer.getInt();
if
(
box
.
getClass
()
==
c
)
{
// final int size = byteBuffer.getInt();
return
(
T
)
box
;
// final ResidentBox residentBox = boxFactory.createBox(type, size, byteBuffer);
}
// list.add(residentBox);
}
// }
return
null
;
// return list;
}
// }
/**
/**
* Assume the input is pointing to the list type
* Assume the input is pointing to the list type
...
...
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ResidentBox.java
View file @
d9afe510
...
@@ -25,46 +25,6 @@ public class ResidentBox extends Box {
...
@@ -25,46 +25,6 @@ public class ResidentBox extends Box {
}
}
/**
/**
* List is not yet populated
* @param byteBuffer
* @return
* @throws IOException
*/
@Nullable
public
static
<
T
extends
ResidentBox
>
T
getInstance
(
final
ByteBuffer
byteBuffer
,
ExtractorInput
input
,
Class
<
T
>
boxClass
)
throws
IOException
{
if
(
byteBuffer
.
remaining
()
<
8
)
{
//Should not happen
throw
new
BufferUnderflowException
();
}
final
int
type
=
byteBuffer
.
getInt
();
final
long
size
=
AviUtil
.
getUInt
(
byteBuffer
);
if
(
size
>
MAX_RESIDENT
)
{
throw
new
BufferOverflowException
();
}
final
ByteBuffer
boxBuffer
=
AviUtil
.
getByteBuffer
(
byteBuffer
,
(
int
)
size
,
input
);
return
newInstance
(
type
,
(
int
)
size
,
boxBuffer
,
boxClass
);
}
@Nullable
private
static
<
T
extends
ResidentBox
>
T
newInstance
(
int
type
,
int
size
,
ByteBuffer
boxBuffer
,
Class
<
T
>
boxClass
)
{
try
{
final
Constructor
<
T
>
constructor
=
boxClass
.
getDeclaredConstructor
(
int
.
class
,
int
.
class
,
ByteBuffer
.
class
);
T
box
=
constructor
.
newInstance
(
type
,
size
,
boxBuffer
);
if
(!
box
.
assertType
())
{
Log
.
e
(
TAG
,
"Expected "
+
AviUtil
.
toString
(
type
)
+
" got "
+
AviUtil
.
toString
(
box
.
getType
()));
return
null
;
}
return
box
;
}
catch
(
Exception
e
)
{
Log
.
e
(
TAG
,
"Create box failed "
+
AviUtil
.
toString
(
type
));
return
null
;
}
}
/**
* Returns shallow copy of this ByteBuffer with the position at 0
* Returns shallow copy of this ByteBuffer with the position at 0
* @return
* @return
*/
*/
...
...
library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBox.java
View file @
d9afe510
...
@@ -90,7 +90,7 @@ public class StreamHeaderBox extends ResidentBox {
...
@@ -90,7 +90,7 @@ public class StreamHeaderBox extends ResidentBox {
return
byteBuffer
.
getInt
(
28
);
return
byteBuffer
.
getInt
(
28
);
}
}
public
long
getLength
()
{
public
long
getLength
()
{
return
byteBuffer
.
getInt
(
32
)
&
Avi
Util
.
UINT_MASK
;
return
byteBuffer
.
getInt
(
32
)
&
Avi
Extractor
.
UINT_MASK
;
}
}
//36 - dwSuggestedBufferSize
//36 - dwSuggestedBufferSize
//40 - dwQuality
//40 - dwQuality
...
...
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