Verified Commit df5cbf93 authored by Daniel Sonck's avatar Daniel Sonck
Browse files

Add V2 protocol

parent b9878ca4
......@@ -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
......
......@@ -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">
......
......@@ -81,7 +81,7 @@ class RtpReceiver(private val mRingBuffer: RingBuffer, private val metaListener:
}
inner class UdpImpl(private val mRecvSocket: DatagramSocket, private var mStreamHost: String, private var mStreamPort: Int) {
inner class UdpImpl(private val mReceiveSocket: 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, samples * 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)
mRecvSocket.send(sendPacket)
mReceiveSocket.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 {
mRecvSocket.send(sendPacket)
mReceiveSocket.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 {
mRecvSocket.send(sendPacket)
mReceiveSocket.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
mRecvSocket.soTimeout = SAMPLE_LEN * 8
mRecvSocket.receive(recvPacket)
mReceiveSocket.soTimeout = SAMPLE_LEN * 8
mReceiveSocket.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
}
}
......@@ -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 ->
......
......@@ -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)
}
......
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 ushr 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()
}
......
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
package fm.touhou.touhoufm.utils
interface MetaListPacket {
val meta: List<MetaItem>
}
\ No newline at end of file
......@@ -5,5 +5,7 @@ interface PacketReceiver {
fun onMetaPacket(base: BasePacket, packet: MetaPacket)
fun onMetaListPacket(base: BasePacket, packet: MetaListPacket)
fun onUnknownPacket(base: BasePacket)
}
......@@ -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
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
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
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
......@@ -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"
......
......@@ -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 28
targetSdkVersion 29
versionCode 2
versionName "2.0"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment