Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
TouHou.FM
Client
fm.touhou.touhoufm
Commits
df5cbf93
Verified
Commit
df5cbf93
authored
Sep 08, 2020
by
Daniel Sonck
Browse files
Add V2 protocol
parent
b9878ca4
Changes
15
Hide whitespace changes
Inline
Side-by-side
app/build.gradle
View file @
df5cbf93
...
...
@@ -8,7 +8,7 @@ buildscript {
}
dependencies
{
classpath
'com.android.tools.build:gradle:3.5.
1
'
classpath
'com.android.tools.build:gradle:3.5.
3
'
}
}
...
...
@@ -45,7 +45,7 @@ dependencies {
implementation
'com.android.support.constraint:constraint-layout:1.1.3'
implementation
'org.java-websocket:Java-WebSocket:1.3.7'
implementation
'com.facebook.fresco:fresco:
1.8
.0'
implementation
'com.facebook.fresco:fresco:
2.1
.0'
implementation
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation
"com.google.android.material:material:$rootProject.materialVersion"
...
...
@@ -79,6 +79,7 @@ List<String> dirs = [
android
{
compileSdkVersion
29
buildToolsVersion
"29.0.2"
defaultConfig
{
minSdkVersion
14
...
...
app/src/main/AndroidManifest.xml
View file @
df5cbf93
...
...
@@ -7,6 +7,7 @@
<uses-permission
android:name=
"android.permission.FOREGROUND_SERVICE"
/>
<uses-permission
android:name=
"android.permission.INTERNET"
/>
<uses-permission
android:name=
"android.permission.MODIFY_AUDIO_SETTINGS"
/>
<application
android:name=
".TouHouFM"
...
...
@@ -16,7 +17,8 @@
android:supportsRtl=
"true"
android:theme=
"@style/AppTheme"
android:fullBackupContent=
"@xml/backup_descriptor"
android:extractNativeLibs=
"false"
>
android:extractNativeLibs=
"false"
tools:targetApi=
"m"
>
<activity
android:name=
".ui.MainActivity"
android:launchMode=
"singleTop"
>
...
...
app/src/main/java/fm/touhou/touhoufm/radio/RtpReceiver.kt
View file @
df5cbf93
...
...
@@ -81,7 +81,7 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
}
inner
class
UdpImpl
(
private
val
mRec
v
Socket
:
DatagramSocket
,
private
var
mStreamHost
:
String
,
private
var
mStreamPort
:
Int
)
{
inner
class
UdpImpl
(
private
val
mRec
eive
Socket
:
DatagramSocket
,
private
var
mStreamHost
:
String
,
private
var
mStreamPort
:
Int
)
{
private
var
mAddressCache
:
InetAddress
?
=
null
private
val
mAddressLock
=
Object
()
...
...
@@ -126,10 +126,14 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
mLastSeqNum
=
sequenceNumber
// Decode the opus data into pcm frames
val
samps
=
mOpusDecoder
?.
decode
(
opus
,
mPcmFrames
)
?:
0
val
samples
=
mOpusDecoder
?.
decode
(
opus
,
mPcmFrames
)
?:
0
if
(
samples
<
0
)
{
throw
IOException
(
"Opus Decoding error $samples"
)
}
// Write the audio data to the buffer
if
(
mRingBuffer
.
write
(
mPcmFrames
,
samps
*
2
)
<
0
)
{
if
(
mRingBuffer
.
write
(
mPcmFrames
,
samp
le
s
*
2
)
<
0
)
{
// If the write returns -1, this means the buffer is aborted and we should stop
interrupt
()
}
...
...
@@ -141,6 +145,10 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
metaListener
.
newMeta
(
packet
.
field
,
packet
.
contents
?:
""
)
}
override
fun
onMetaListPacket
(
base
:
BasePacket
,
packet
:
MetaListPacket
)
{
metaListener
.
newMeta
(
packet
.
meta
)
}
override
fun
onUnknownPacket
(
base
:
BasePacket
)
{
Log
.
w
(
TAG
,
"Unknown packet"
)
}
...
...
@@ -151,7 +159,7 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
// send mic off
val
msg
=
"Bye"
val
sendPacket
=
DatagramPacket
(
msg
.
toByteArray
(),
msg
.
length
,
address
,
port
)
mRec
v
Socket
.
send
(
sendPacket
)
mRec
eive
Socket
.
send
(
sendPacket
)
}
catch
(
e
:
IOException
)
{
Log
.
e
(
TAG
,
"Failed to clr udp conn"
,
e
)
}
...
...
@@ -193,7 +201,7 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
do
{
try
{
mRec
v
Socket
.
send
(
sendPacket
)
mRec
eive
Socket
.
send
(
sendPacket
)
mLastHello
=
System
.
currentTimeMillis
()
retry
=
false
Log
.
d
(
TAG
,
"sending hello packet"
)
...
...
@@ -222,7 +230,7 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
val
sendPacket
=
DatagramPacket
(
msg
.
toByteArray
(),
msg
.
length
,
address
,
port
)
try
{
mRec
v
Socket
.
send
(
sendPacket
)
mRec
eive
Socket
.
send
(
sendPacket
)
}
catch
(
e
:
IOException
)
{
Log
.
e
(
TAG
,
"Failed to send hello"
,
e
)
}
...
...
@@ -263,14 +271,14 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
@Throws
(
IOException
::
class
)
private
fun
receiveDatagramPacket
():
DatagramPacket
{
val
message
=
ByteArray
(
OPUS_SIZE
+
RTP_SIZE
)
val
message
=
ByteArray
(
3200
)
val
recvPacket
=
DatagramPacket
(
message
,
message
.
size
)
try
{
// Receive the packet
mRec
v
Socket
.
soTimeout
=
SAMPLE_LEN
*
8
mRec
v
Socket
.
receive
(
recvPacket
)
mRec
eive
Socket
.
soTimeout
=
SAMPLE_LEN
*
8
mRec
eive
Socket
.
receive
(
recvPacket
)
}
catch
(
e
:
SocketTimeoutException
)
{
sendHello
()
...
...
@@ -307,6 +315,7 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
interface
MetaListener
{
fun
newMeta
(
field
:
Int
,
value
:
String
)
fun
newMeta
(
meta
:
List
<
MetaItem
>)
}
interface
ProgressListener
{
...
...
@@ -321,12 +330,8 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
const
val
SAMPLE_RATE
=
48000
const
val
FRAME_SIZE
=
SAMPLE_RATE
*
SAMPLE_LEN
/
1000
private
const
val
BITRATE
=
320000
private
const
val
OPUS_SIZE
=
SAMPLE_LEN
*
BITRATE
/
8
/
1000
private
const
val
BUF_SIZE
=
FRAME_SIZE
*
2
private
const
val
RTP_SIZE
=
16
}
}
app/src/main/java/fm/touhou/touhoufm/service/players/MediaPlayerAdapter.kt
View file @
df5cbf93
...
...
@@ -51,6 +51,7 @@ import fm.touhou.touhoufm.service.PlayerAdapter
import
fm.touhou.touhoufm.service.players.RadioPlayer.Companion.STREAM_HOST
import
fm.touhou.touhoufm.service.players.RadioPlayer.Companion.STREAM_PORT
import
fm.touhou.touhoufm.ui.MainActivity
import
fm.touhou.touhoufm.utils.MetaItem
import
fm.touhou.touhoufm.utils.MetaPacket
/**
...
...
@@ -223,6 +224,18 @@ class MediaPlayerAdapter(private val mContext: Context, private val mPlaybackInf
}
override
fun
newMeta
(
meta
:
List
<
MetaItem
>)
{
for
(
metaItem
in
meta
)
metaItem
.
contents
?.
let
{
value
->
when
(
metaItem
.
field
)
{
MetaItem
.
FIELD_SONG_TITLE
->
songTitle
=
value
MetaItem
.
FIELD_ALBUM_TITLE
->
songAlbum
=
value
MetaItem
.
FIELD_SONG_ARTIST
->
songArtist
=
value
MetaItem
.
FIELD_ALBUM_ARTIST
->
songCircle
=
value
}
}
}
private
fun
queueUpdateMetadata
()
{
if
(
mMetadataUpdater
==
null
)
{
mMetadataUpdater
=
MetadataUpdater
(
mState
).
also
{
updater
->
...
...
app/src/main/java/fm/touhou/touhoufm/ui/MainActivity.kt
View file @
df5cbf93
...
...
@@ -36,7 +36,6 @@
package
fm.touhou.touhoufm.ui
import
android.content.Context
import
android.content.DialogInterface
import
android.content.Intent
import
android.net.Uri
import
android.os.Bundle
...
...
@@ -72,7 +71,7 @@ class MainActivity : AppCompatActivity() {
super
.
onCreate
(
savedInstanceState
)
queue
=
Volley
.
newRequestQueue
(
this
)
val
prefs
=
getSharedPreferences
(
"AppDetails"
,
Context
.
MODE_PRIVATE
)
;
val
prefs
=
getSharedPreferences
(
"AppDetails"
,
Context
.
MODE_PRIVATE
)
setContentView
(
R
.
layout
.
navigation_activity
)
...
...
@@ -99,14 +98,14 @@ class MainActivity : AppCompatActivity() {
val
jsonRequest
=
JsonObjectRequest
(
"https://www.touhou.fm/app-release.json"
,
null
,
Response
.
Listener
{
response
:
JSONObject
->
val
supported
:
Boolean
;
val
supported
:
Boolean
when
{
appVersion
<
response
.
getInt
(
"supported"
)
->
{
supported
=
false
;
supported
=
false
AlertDialog
.
Builder
(
this
)
.
setTitle
(
"Unsupported app version"
)
.
setMessage
(
"This version of the app is currently unsupported, please download the new version from the play store"
)
.
setPositiveButton
(
"Download"
,
DialogInterface
.
OnClickListener
{
dialogInterface
,
i
->
.
setPositiveButton
(
"Download"
)
{
_
,
_
->
val
appPackageName
=
packageName
try
{
startActivity
(
Intent
(
Intent
.
ACTION_VIEW
,
Uri
.
parse
(
"market://details?id=$appPackageName"
)))
...
...
@@ -114,20 +113,20 @@ class MainActivity : AppCompatActivity() {
startActivity
(
Intent
(
Intent
.
ACTION_VIEW
,
Uri
.
parse
(
"https://play.google.com/store/apps/details?id=$appPackageName"
)))
}
}
)
.
setNegativeButton
(
"Quit"
,
DialogInterface
.
OnClickListener
{
dialogInterface
,
i
->
}
.
setNegativeButton
(
"Quit"
)
{
_
,
_
->
this
@MainActivity
.
finish
()
}
)
}
.
setIcon
(
android
.
R
.
drawable
.
ic_dialog_alert
)
.
show
()
;
.
show
()
}
appVersion
<
response
.
getInt
(
"beta"
)
-
1
->
{
supported
=
true
;
supported
=
true
if
(
lastVersion
!=
appVersion
||
lastKnownVersion
!=
response
.
getInt
(
"beta"
)
-
1
)
{
AlertDialog
.
Builder
(
this
)
.
setTitle
(
"New version"
)
.
setMessage
(
"A new version is available in the play store"
)
.
setPositiveButton
(
"Download"
,
DialogInterface
.
OnClickListener
{
dialogInterface
,
i
->
.
setPositiveButton
(
"Download"
)
{
_
,
_
->
val
appPackageName
=
packageName
try
{
startActivity
(
Intent
(
Intent
.
ACTION_VIEW
,
Uri
.
parse
(
"market://details?id=$appPackageName"
)))
...
...
@@ -135,18 +134,18 @@ class MainActivity : AppCompatActivity() {
startActivity
(
Intent
(
Intent
.
ACTION_VIEW
,
Uri
.
parse
(
"https://play.google.com/store/apps/details?id=$appPackageName"
)))
}
}
)
}
.
setNegativeButton
(
android
.
R
.
string
.
cancel
,
null
)
.
setIcon
(
android
.
R
.
drawable
.
ic_dialog_info
)
.
show
()
;
.
show
()
}
}
appVersion
<
response
.
getInt
(
"alpha"
)
->
{
supported
=
true
;
supported
=
true
Toast
.
makeText
(
this
,
"Thanks for being a beta tester"
,
LENGTH_LONG
).
show
()
}
else
->
{
supported
=
true
;
supported
=
true
Toast
.
makeText
(
this
,
"Thanks for being an alpha tester"
,
LENGTH_LONG
).
show
()
}
}
...
...
@@ -155,10 +154,10 @@ class MainActivity : AppCompatActivity() {
Changelog
.
createDialog
(
this
,
versionCode
=
lastVersion
).
show
()
}
},
Response
.
ErrorListener
{
Toast
.
makeText
(
this
,
"Failed to check latest version"
,
Toast
.
LENGTH_LONG
).
show
()
;
Toast
.
makeText
(
this
,
"Failed to check latest version"
,
LENGTH_LONG
).
show
()
})
queue
.
add
(
jsonRequest
)
;
queue
.
add
(
jsonRequest
)
}
...
...
app/src/main/java/fm/touhou/touhoufm/utils/BasePacket.kt
View file @
df5cbf93
package
fm.touhou.touhoufm.utils
import
fm.touhou.touhoufm.utils.base_packet.BasePacketV1
import
fm.touhou.touhoufm.utils.base_packet.BasePacketV2
import
java.io.IOException
import
java.net.DatagramPacket
import
java.nio.ByteBuffer
...
...
@@ -16,7 +17,9 @@ interface BasePacket {
internal
const
val
HEADER_SIZE
=
4
const
val
VERSION_1_0
=
0
const
val
VERSION_1_1
=
1
@Suppress
(
"unused"
)
const
val
SYSTEM_SESSION
=
0
const
val
SYSTEM_AUDIO
=
1
...
...
@@ -24,10 +27,6 @@ interface BasePacket {
const
val
TYPE_AUDIO_OPUS
=
1
const
val
TYPE_META
=
2
private
fun
unsignedInt
(
i
:
Int
):
Int
{
return
if
(
i
>=
0
)
i
else
256
+
i
}
fun
decode
(
packet
:
ByteArray
,
length
:
Int
,
packetReceiver
:
PacketReceiver
):
BasePacket
{
if
(
min
(
packet
.
size
,
length
)
<
HEADER_SIZE
)
throw
InvalidBasePacket
()
...
...
@@ -37,24 +36,23 @@ interface BasePacket {
val
system
:
Int
buffer
.
get
().
toInt
().
let
{
version
=
it
shr
4
version
=
it
u
shr
4
system
=
it
and
0x0F
}
when
(
version
)
{
VERSION_1_0
->
return
BasePacketV1
(
system
,
buffer
,
packetReceiver
)
return
when
(
version
)
{
VERSION_1_0
->
BasePacketV1
(
system
,
buffer
,
packetReceiver
)
VERSION_1_1
->
BasePacketV2
(
system
,
buffer
,
packetReceiver
)
else
->
throw
InvalidBasePacket
()
}
}
fun
encode
(
bb
:
ByteBuffer
)
{
fun
encode
()
{
}
}
class
InvalidBasePacket
:
IOException
()
{
}
class
InvalidBasePacket
:
IOException
()
}
...
...
app/src/main/java/fm/touhou/touhoufm/utils/MetaItem.kt
0 → 100644
View file @
df5cbf93
package
fm.touhou.touhoufm.utils
interface
MetaItem
{
val
field
:
String
val
contents
:
String
?
companion
object
{
const
val
FIELD_SONG_TITLE
=
"song"
const
val
FIELD_ALBUM_TITLE
=
"album"
const
val
FIELD_SONG_ARTIST
=
"artist"
const
val
FIELD_ALBUM_ARTIST
=
"circle"
}
}
\ No newline at end of file
app/src/main/java/fm/touhou/touhoufm/utils/MetaListPacket.kt
0 → 100644
View file @
df5cbf93
package
fm.touhou.touhoufm.utils
interface
MetaListPacket
{
val
meta
:
List
<
MetaItem
>
}
\ No newline at end of file
app/src/main/java/fm/touhou/touhoufm/utils/PacketReceiver.kt
View file @
df5cbf93
...
...
@@ -5,5 +5,7 @@ interface PacketReceiver {
fun
onMetaPacket
(
base
:
BasePacket
,
packet
:
MetaPacket
)
fun
onMetaListPacket
(
base
:
BasePacket
,
packet
:
MetaListPacket
)
fun
onUnknownPacket
(
base
:
BasePacket
)
}
app/src/main/java/fm/touhou/touhoufm/utils/audio_packet/AudioPacketV1.kt
View file @
df5cbf93
...
...
@@ -12,16 +12,15 @@ class AudioPacketV1(bb: ByteBuffer) : AudioPacket {
override
val
timestamp
:
Long
=
bb
.
long
override
val
position
:
Int
=
bb
.
int
override
val
length
:
Int
=
bb
.
int
override
val
audio
=
ByteArray
(
bb
.
limit
()
-
bb
.
position
())
override
val
audio
=
let
{
val
audio
=
ByteArray
(
bb
.
limit
()
-
bb
.
position
())
init
{
bb
.
get
(
audio
)
audio
}
companion
object
{
private
const
val
HEADER_SIZE
=
16
}
}
\ No newline at end of file
app/src/main/java/fm/touhou/touhoufm/utils/base_packet/BasePacketV2.kt
0 → 100644
View file @
df5cbf93
package
fm.touhou.touhoufm.utils.base_packet
import
fm.touhou.touhoufm.utils.BasePacket
import
fm.touhou.touhoufm.utils.BasePacket.Companion.HEADER_SIZE
import
fm.touhou.touhoufm.utils.PacketReceiver
import
fm.touhou.touhoufm.utils.audio_packet.AudioPacketV1
import
fm.touhou.touhoufm.utils.meta_list_packet.MetaPacketV2
import
java.nio.ByteBuffer
class
BasePacketV2
(
override
val
system
:
Int
,
bb
:
ByteBuffer
,
packetReceiver
:
PacketReceiver
)
:
BasePacket
{
init
{
if
(
bb
.
limit
()
-
bb
.
position
()
<
HEADER_SIZE
-
1
)
{
throw
BasePacket
.
InvalidBasePacket
()
}
}
override
val
version
:
Int
=
BasePacket
.
VERSION_1_1
override
val
type
:
Int
=
bb
.
get
().
toInt
()
override
val
sequenceNumber
:
Int
=
bb
.
short
.
toInt
()
init
{
when
(
type
)
{
BasePacket
.
TYPE_AUDIO_OPUS
->
{
packetReceiver
.
onAudioPacket
(
this
,
AudioPacketV1
(
bb
))
}
BasePacket
.
TYPE_META
->
packetReceiver
.
onMetaListPacket
(
this
,
MetaPacketV2
(
bb
))
else
->
packetReceiver
.
onUnknownPacket
(
this
)
}
}
}
\ No newline at end of file
app/src/main/java/fm/touhou/touhoufm/utils/meta_item/MetaItemV2.kt
0 → 100644
View file @
df5cbf93
package
fm.touhou.touhoufm.utils.meta_item
import
fm.touhou.touhoufm.utils.MetaItem
class
MetaItemV2
(
override
val
field
:
String
,
override
val
contents
:
String
)
:
MetaItem
\ No newline at end of file
app/src/main/java/fm/touhou/touhoufm/utils/meta_list_packet/MetaPacketV2.kt
0 → 100644
View file @
df5cbf93
package
fm.touhou.touhoufm.utils.meta_list_packet
import
fm.touhou.touhoufm.utils.MetaItem
import
fm.touhou.touhoufm.utils.MetaListPacket
import
fm.touhou.touhoufm.utils.MetaPacket
import
fm.touhou.touhoufm.utils.MetaPacket.Companion.codec
import
fm.touhou.touhoufm.utils.meta_item.MetaItemV2
import
java.nio.ByteBuffer
class
MetaPacketV2
(
bb
:
ByteBuffer
)
:
MetaListPacket
{
init
{
if
(
bb
.
limit
()
-
bb
.
position
()
<
4
+
8
)
throw
MetaPacket
.
InvalidMetaPacket
()
}
override
val
meta
:
List
<
MetaItem
>
=
let
{
val
fields
=
bb
.
int
MutableList
<
MetaItem
>(
fields
)
{
val
fieldLength
=
bb
.
long
val
fieldRaw
=
ByteArray
(
fieldLength
.
toInt
())
bb
.
get
(
fieldRaw
)
val
valueLength
=
bb
.
long
val
valueRaw
=
ByteArray
(
valueLength
.
toInt
())
bb
.
get
(
valueRaw
)
MetaItemV2
(
String
(
fieldRaw
,
codec
),
String
(
valueRaw
,
codec
))
}.
toList
()
}
}
\ No newline at end of file
build.gradle
View file @
df5cbf93
...
...
@@ -16,7 +16,7 @@ buildscript {
google
()
}
dependencies
{
classpath
'com.android.tools.build:gradle:3.5.
1
'
classpath
'com.android.tools.build:gradle:3.5.
3
'
classpath
"androidx.navigation:navigation-safe-args-gradle-plugin:$navigationVersion"
classpath
"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
...
...
changelog/build.gradle
View file @
df5cbf93
...
...
@@ -2,11 +2,12 @@ apply plugin: 'com.android.library'
apply
plugin:
'kotlin-android'
android
{
compileSdkVersion
28
compileSdkVersion
29
buildToolsVersion
"29.0.2"
defaultConfig
{
minSdkVersion
14
targetSdkVersion
2
8
targetSdkVersion
2
9
versionCode
2
versionName
"2.0"
...
...
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