Skip to content

Commit

Permalink
Merge pull request #34 from Tim-W/master
Browse files Browse the repository at this point in the history
MusicDAO first prototype
  • Loading branch information
MattSkala authored May 13, 2020
2 parents 8184eb8 + 3f42e4f commit 78e06e5
Show file tree
Hide file tree
Showing 41 changed files with 1,273 additions and 3 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ The TrustChain Voter can be used to create a proposal on which the community can
- [More about the Voting API](common/README.md#votinghelper)
- [More about the TrustChain Voter submodule](trustchain-voter/README.md)

<img src="doc/trustchain-voter/create-proposal.gif" width="280"> <img src="doc/trustchain-voter/cast-vote-process.gif" width="280">
<img src="doc/trustchain-voter/create-proposal.gif" width="280"> <img src="doc/trustchain-voter/cast-vote-process.gif" width="280">

### Freedom-of-Computing App

Expand All @@ -89,6 +89,18 @@ The left demo shows the upload procedure, while the right demo shows the downloa

[More about Freedom-of-Computing App](freedomOfComputing/README.md)

### MusicDAO
In short, the MusicDAO is an IPv8 app where users can share and discover tracks on the trustchain. Track streaming, downloading, and seeking interactions are done using JLibtorrent.

A user can publish a Release (which is an album/EP/single/...), after which the app creates a magnet link referring to these audio tracks. Then, the app creates a proposal block for the trustchain which contains some metadata (release date, title, ...) this metadata is submitted by the user with a dialog. When a signed block is discovered (currently are self-signed), the app tries to obtain the file list using JLibtorrent. Each file can be streamed independently on clicking the play button.

**Screens**
<img src="doc/musicdao/screen2.png" width="300"> <img src="doc/musicdao/screen1.png" width="300"> <img src="doc/musicdao/screen3.png" width="300">

**Videos**
Video 1: <a href="doc/musicdao/thesis2.mp4">Load example.</a> This uses a default magnet link for an album that has a decent amount of peers. The user submits the metadata and the block gets proposed and signed. Then playback.
Video 2: <a href="doc/musicdao/thesis3.mp4">Share track.</a> Note: as a fresh magnet link is generated in this video, there is only 1 peer. For this reason it will be difficult to obtain the metadata of the magnet link (cold start issue, write about this in thesis) so the video stops there.

### Do you want to add your own app?

- [Adding your own app to the TrustChain Super App](doc/AppTutorial.md)
Expand Down
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ dependencies {
implementation project(':debug')
implementation project(':currencyii')
implementation project(':freedomOfComputing')
implementation project(':musicdao')

// AndroidX
implementation 'androidx.appcompat:appcompat:1.1.0'
Expand Down
8 changes: 6 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

<application
android:name="nl.tudelft.trustchain.app.TrustChainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:name="nl.tudelft.trustchain.app.TrustChainApplication"
android:allowBackup="true"
tools:ignore="AllowBackup">

<activity
Expand Down Expand Up @@ -61,6 +61,10 @@
android:name="nl.tudelft.trustchain.voting.VotingActivity"
android:parentActivityName=".ui.dashboard.DashboardActivity" />

<activity
android:name="com.example.musicdao.MusicService"
android:parentActivityName=".ui.dashboard.DashboardActivity" />

<service android:name="nl.tudelft.trustchain.app.service.TrustChainService" />
</application>

Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/nl/tudelft/trustchain/app/AppDefinition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package nl.tudelft.trustchain.app
import android.app.Activity
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import com.example.musicdao.MusicService
import nl.tudelft.trustchain.FOC.MainActivityFOC
import nl.tudelft.trustchain.common.R
import nl.tudelft.trustchain.currencyii.CurrencyIIMainActivity
Expand Down Expand Up @@ -59,5 +60,11 @@ enum class AppDefinition(
"Voter",
R.color.android_green,
VotingActivity::class.java
),
MUSIC_DAO(
android.R.drawable.ic_media_play,
"MusicDAO",
R.color.black,
MusicService::class.java
)
}
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ allprojects {
jcenter()
maven { url "https://dl.bintray.com/mattskala/maven" }
maven { url "https://dl.bintray.com/terl/lazysodium-maven" }
maven { url 'https://jitpack.io' }
}

// Temp fix for issue https://github.com/mockk/mockk/issues/281
Expand Down
Binary file added doc/musicdao/screen1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/musicdao/screen2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/musicdao/screen3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/musicdao/thesis2.mp4
Binary file not shown.
Binary file added doc/musicdao/thesis3.mp4
Binary file not shown.
1 change: 1 addition & 0 deletions musicdao/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
71 changes: 71 additions & 0 deletions musicdao/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'org.jlleitschuh.gradle.ktlint'

android {
compileSdkVersion 29
buildToolsVersion "29.0.3"

viewBinding {
enabled = true
}

defaultConfig {
minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

// To inline the bytecode built with JVM target 1.8 into
// bytecode that is being built with JVM target 1.6. (e.g. navArgs)


compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
allWarningsAsErrors = true
}

}

dependencies {
implementation project(':common')
implementation project(':ipv8-android')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation "androidx.preference:preference:1.1.0"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation 'com.turn:ttorrent-core:1.5'
implementation files('libs/jlibtorrent-1.2.5.0.jar')
implementation files('libs/jlibtorrent-android-arm64-1.2.5.0.jar')
implementation files('libs/jlibtorrent-android-arm-1.2.5.0.jar')
implementation files('libs/jlibtorrent-android-x86-1.2.5.0.jar')
implementation files('libs/jlibtorrent-android-x86_64-1.2.5.0.jar')
implementation('com.github.TorrentStream:TorrentStream-Android:2.5.0') {
exclude group: 'com.frostwire'
}
}
Empty file added musicdao/consumer-rules.pro
Empty file.
Binary file added musicdao/libs/jlibtorrent-1.2.5.0.jar
Binary file not shown.
Binary file added musicdao/libs/jlibtorrent-android-arm-1.2.5.0.jar
Binary file not shown.
Binary file not shown.
Binary file added musicdao/libs/jlibtorrent-android-x86-1.2.5.0.jar
Binary file not shown.
Binary file not shown.
21 changes: 21 additions & 0 deletions musicdao/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.musicdao

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.musicdao.test", appContext.packageName)
}
}
6 changes: 6 additions & 0 deletions musicdao/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.musicdao">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
163 changes: 163 additions & 0 deletions musicdao/src/main/java/com/example/musicdao/AudioPlayer.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package com.example.musicdao

import android.R
import android.content.Context
import android.media.AudioAttributes
import android.media.MediaPlayer
import android.os.Handler
import android.widget.LinearLayout
import android.widget.SeekBar
import android.widget.Toast
import androidx.core.net.toUri
import kotlinx.android.synthetic.main.music_app_main.*
import java.io.File

lateinit var instance: AudioPlayer

/**
* Implements an Android MediaPlayer. Is a singleton.
*/
class AudioPlayer(context: Context, private val musicService: MusicService) : LinearLayout(context),
MediaPlayer.OnPreparedListener,
MediaPlayer.OnErrorListener, SeekBar.OnSeekBarChangeListener {
private val mediaPlayer: MediaPlayer = MediaPlayer()
private var interestedFraction: Float = 0F
private val bufferInfo = musicService.bufferInfo
private val progressBar = musicService.progressBar
private val seekBar = musicService.seekBar
private val playButton = musicService.playButtonAudioPlayer

init {
progressBar.max = 100
progressBar.progress = 0
bufferInfo.text = "No track currently playing"
seekBar.setOnSeekBarChangeListener(this)

// Handle playing and pausing tracks
this.playButton.setOnClickListener {
if (mediaPlayer.isPlaying) {
this.playButton.setImageResource(R.drawable.ic_media_play)
mediaPlayer.pause()
} else {
this.playButton.setImageResource(R.drawable.ic_media_pause)
mediaPlayer.start()
}
}

followSeekBarWithTrack()
}

/**
* This function updates the seek bar location every second with the playing position
*/
private fun followSeekBarWithTrack() {
val mHandler = Handler()
musicService.runOnUiThread(object : Runnable {
override fun run() {
val mCurrentPosition: Int = mediaPlayer.currentPosition / 1000
seekBar.progress = mCurrentPosition
mHandler.postDelayed(this, 1000)
}
})
}

companion object {
fun getInstance(context: Context, musicService: MusicService): AudioPlayer {
if (!::instance.isInitialized) {
createInstance(
context,
musicService
)
}
return instance
}

@Synchronized
private fun createInstance(context: Context, musicService: MusicService) {
instance =
AudioPlayer(context, musicService)
}
}

/**
* Reset internal state to prepare for playing a track
*/
fun prepareNextTrack() {
if (mediaPlayer.isPlaying) mediaPlayer.stop()
seekBar.progress = 0
playButton.setImageResource(R.drawable.ic_media_play)
playButton.isClickable = false
playButton.isActivated = false
playButton.isEnabled = false
}

fun setAudioResource(file: File) {
prepareNextTrack()
mediaPlayer.reset()
mediaPlayer.apply {
setOnPreparedListener(this@AudioPlayer)
setOnErrorListener(this@AudioPlayer)
setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
setDataSource(context, file.toUri())
prepareAsync()
}
}

override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean {
mp?.reset()
var message = ""
when (what) {
MediaPlayer.MEDIA_ERROR_IO -> {
message = "Media error: IO"
}
MediaPlayer.MEDIA_ERROR_MALFORMED -> {
message = "Media error: malformed"
}
MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK -> {
message = "Media error: invalid for progressive playback"
}
MediaPlayer.MEDIA_ERROR_SERVER_DIED -> {
message = "Media error: server died"
}
MediaPlayer.MEDIA_ERROR_TIMED_OUT -> {
message = "Media error: timed out"
}
MediaPlayer.MEDIA_ERROR_UNKNOWN -> {
message = "Media error: unknown"
}
MediaPlayer.MEDIA_ERROR_UNSUPPORTED -> {
message = "Media error: unsupported"
}
}
val toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
toast.show()
return true
}

override fun onPrepared(mp: MediaPlayer) {
this.playButton.isClickable = true
this.playButton.isActivated = true
this.playButton.isEnabled = true
// Directly play the track when it is prepared
this.playButton.callOnClick()
}

/**
* This enables seeking through the track
*/
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (!fromUser) return
interestedFraction = (progress.toFloat() / 100.toFloat())
val duration = mediaPlayer.duration
val seekMs: Int = (duration * interestedFraction).toInt()
mediaPlayer.seekTo(seekMs)
}

override fun onStartTrackingTouch(seekBar: SeekBar?) {}

override fun onStopTrackingTouch(seekBar: SeekBar?) {}
}
11 changes: 11 additions & 0 deletions musicdao/src/main/java/com/example/musicdao/MainFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.musicdao

import androidx.fragment.app.Fragment
import nl.tudelft.trustchain.common.ui.BaseFragment

/**
* A simple [Fragment] subclass.
* Use the [MainFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class MainFragment : BaseFragment(R.layout.music_app_main)
Loading

0 comments on commit 78e06e5

Please sign in to comment.