Skip to content
This repository has been archived by the owner on Jan 31, 2024. It is now read-only.

Maximezmt/music Itunes Api #45

Merged
merged 24 commits into from
Mar 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The game will also offer a customization where the user can create a game from i
- [x] Create a Github repository
- [x] Setup all Github feature (params, ...)
- [x] Add continuous integration
- [ ]
- [ ]



Expand All @@ -49,8 +49,8 @@ The game will also offer a customization where the user can create a game from i
| Name | GitHub username |
|:--------|---------:|
| Derin Arda Alpay | [Tsathogguaa](https://github.com/Tsathogguaa) |
| Kamila Babayeva | [kamilababayeva14](https://github.com/kamilababayeva14) |
| Kamila Babayeva | [kamilababayeva](https://github.com/kamilababayeva) |
| Laurynas Lopata | [laurislopata](https://github.com/laurislopata) |
| Jiabao Wen | [jiabaow](https://github.com/jiabaow) |
| Maxime Zammit | [MaximeZmt](https://github.com/MaximeZmt) |
| Margaux Zwierski | [zwierski](https://github.com/zwierski) |
| Margaux Zwierski | [zwierski](https://github.com/zwierski) |
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ android {

defaultConfig {
applicationId "ch.sdp.vibester"
minSdk 23
minSdk 24
targetSdk 31
versionCode 1
versionName "1.0"
Expand Down Expand Up @@ -50,7 +50,9 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.json:json:20180813'
testImplementation 'org.robolectric:robolectric:4.6'
testImplementation 'androidx.test:core:1.4.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
Expand Down
37 changes: 37 additions & 0 deletions app/src/androidTest/java/ch/sdp/vibester/MusicTemporaryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ch.sdp.vibester

import android.content.Intent
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MusicTemporaryTest{

// Temporary test
@Test
fun musicTemporaryTest() {
val inputName = "Imagine Dragons Believer"
val intent =
Intent(ApplicationProvider.getApplicationContext(), MusicTemporary::class.java)
val scn: ActivityScenario<GreetingActivity> = ActivityScenario.launch(intent)
onView(withId(R.id.musicName))
.perform(typeText(inputName), closeSoftKeyboard())
onView(withId(R.id.validate)).perform(click())
onView(withId(R.id.textViewPlaying))
.check(matches(withText("Imagine Dragons - Believer")))
}


}
50 changes: 50 additions & 0 deletions app/src/androidTest/java/ch/sdp/vibester/api/ItunesMusicApiTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ch.sdp.vibester.api



import ch.sdp.vibester.model.Song
import okhttp3.OkHttpClient
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import java.lang.Exception


class ItunesMusicApiTest{

@Test
fun itunesAPIQueryWorks() {
var songFut = ItunesMusicApi.querySong("imagine dragons believer", OkHttpClient())
val song = Song(songFut.get())
assertEquals("Imagine Dragons", song.getArtistName())
}

@get:Rule
var exception = ExpectedException.none()

@Test
fun itunesAPIQueryError() {
exception.expect(Exception::class.java)
var songFut = ItunesMusicApi.querySong("imagine dragons believer", OkHttpClient(), "https://ThisIsNotAnURL666.notADomain")
songFut.get()
}

@Test
fun itunesAPIQueryWorksComplete() {
var songFut = ItunesMusicApi.querySong("imagine dragons believer", OkHttpClient())
val song = Song(songFut.get())
val mediaFut = ItunesMusicApi.playAudio(song.getPreviewUrl())
val player = mediaFut.get()
assertEquals(true, player.isPlaying)
}

@Test
fun itunesAPIQueryCompleteError() {
exception.expect(Exception::class.java)
val mediaFut = ItunesMusicApi.playAudio("https://ThisIsNotAnURL666.notADomain")
mediaFut.get()
}


}
48 changes: 48 additions & 0 deletions app/src/androidTest/java/ch/sdp/vibester/model/SongTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ch.sdp.vibester.model


import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException


class SongTest{

@Test
fun jsonPreviewParseBasic() {
val inputTxt = """
{
"resultCount":1,
"results": [
{"wrapperType":"track", "kind":"song", "artistId":358714030, "collectionId":1574210519, "trackId":1574210894, "artistName":"Imagine Dragons", "collectionName":"Mercury - Act 1", "trackName":"Monday", "collectionCensoredName":"Mercury - Act 1", "trackCensoredName":"Monday", "artistViewUrl":"https://music.apple.com/us/artist/imagine-dragons/358714030?uo=4", "collectionViewUrl":"https://music.apple.com/us/album/monday/1574210519?i=1574210894&uo=4", "trackViewUrl":"https://music.apple.com/us/album/monday/1574210519?i=1574210894&uo=4",
"previewUrl":"https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/bc/71/fc/bc71fca4-e0bb-609b-5b6e-92296df7b4b6/mzaf_8907306752631175088.plus.aac.p.m4a", "artworkUrl30":"https://is3-ssl.mzstatic.com/image/thumb/Music115/v4/3e/04/c4/3e04c4e7-1863-34cb-e8f3-f168ae5b213e/source/30x30bb.jpg", "artworkUrl60":"https://is3-ssl.mzstatic.com/image/thumb/Music115/v4/3e/04/c4/3e04c4e7-1863-34cb-e8f3-f168ae5b213e/source/60x60bb.jpg", "artworkUrl100":"https://is3-ssl.mzstatic.com/image/thumb/Music115/v4/3e/04/c4/3e04c4e7-1863-34cb-e8f3-f168ae5b213e/source/100x100bb.jpg", "releaseDate":"2021-09-03T12:00:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"notExplicit", "discCount":1, "discNumber":1, "trackCount":13, "trackNumber":4, "trackTimeMillis":187896, "country":"USA", "currency":"USD", "primaryGenreName":"Alternative", "isStreamable":true}]
}
"""

val mySong = Song(inputTxt)

val previewUrl = "https://audio-ssl.itunes.apple.com/itunes-assets/AudioPreview125/v4/bc/71/fc/bc71fca4-e0bb-609b-5b6e-92296df7b4b6/mzaf_8907306752631175088.plus.aac.p.m4a"
val artworkUrl = "https://is3-ssl.mzstatic.com/image/thumb/Music115/v4/3e/04/c4/3e04c4e7-1863-34cb-e8f3-f168ae5b213e/source/100x100bb.jpg"
val artistName = "Imagine Dragons"
val trackName = "Monday"


assertEquals(previewUrl, mySong.getPreviewUrl())
assertEquals(artworkUrl, mySong.getArtworkUrl())
assertEquals(artistName, mySong.getArtistName())
assertEquals(trackName, mySong.getTrackName())
}


@get:Rule
var exception = ExpectedException.none()

@Test
fun jsonPreviewParseErrorText() {
exception.expect(IllegalArgumentException::class.java)
exception.expectMessage("Song constructor, bad argument")
Song("")
}

}
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<activity
android:name=".MusicTemporary"
android:exported="false" />
<activity
android:name=".GameSetupScreen"
android:exported="false" />
<activity android:name=".scoreboard.ScoreBoardActivity"
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/ch/sdp/vibester/MusicTemporary.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ch.sdp.vibester

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import ch.sdp.vibester.api.ItunesMusicApi
import ch.sdp.vibester.model.Song
import okhttp3.OkHttpClient

class MusicTemporary : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_music_temporary)

val txtInput = findViewById<EditText>(R.id.musicName)

val btnValidate = findViewById<Button>(R.id.validate)

val textViewPlaying = findViewById<TextView>(R.id.textViewPlaying)

btnValidate.setOnClickListener {
val song = Song(ItunesMusicApi.querySong(txtInput.text.toString(), OkHttpClient()).get())
ItunesMusicApi.playAudio(song.getPreviewUrl())
textViewPlaying.setText(song.getArtistName() + " - " + song.getTrackName())
}


}
}
84 changes: 84 additions & 0 deletions app/src/main/java/ch/sdp/vibester/api/ItunesMusicApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ch.sdp.vibester.api

import android.media.AudioAttributes
import android.media.MediaPlayer
import android.util.Log
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Response
import java.io.IOException
import java.util.concurrent.CompletableFuture

/**
* Static class to access web API of Itunes
*/
class ItunesMusicApi private constructor(){

companion object{
private val LOOKUP_URL_BASE = "https://itunes.apple.com/search?limit=1&term="

/**
* Given a String query it will provide the music url preview
* @param query String containing the request to the API
* @param okHttp An OkHttpClient
* @param baseUrl (Optional) If you want to specify an other url to query
* @return CompletableFuture<String> that contains the result of the query
*/
fun querySong(query: String, okHttp: OkHttpClient, baseUrl: String = LOOKUP_URL_BASE): CompletableFuture<String> {
val buildedUrl = baseUrl+query.replace(' ', '+')
val req = okhttp3.Request.Builder().url(buildedUrl).build()

var retFuture = CompletableFuture<String>()

okHttp.newCall(req).enqueue(SongCallback(retFuture))

return retFuture
}

/**
* A function that given an audio stream url will play it
* @param audioUrl Url in String pointing towards the audio stream
* @return CompletableFuture<MediaPlayer> that contains the current playing mediaPlayer
*/
fun playAudio(audioUrl: String): CompletableFuture<MediaPlayer>{
val mediaFut = CompletableFuture<MediaPlayer>()
var mediaPlayer: MediaPlayer = MediaPlayer()
mediaPlayer.setAudioAttributes(
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build()
)
try {
mediaPlayer.setDataSource(audioUrl)
mediaPlayer.prepare()
mediaPlayer.setOnPreparedListener {
mediaPlayer.start()
mediaFut.complete(mediaPlayer)
}
}catch (e: IOException){
Log.e("[PlayAudio]", "Error see stacktrace")
mediaFut.completeExceptionally(e)
}
return mediaFut
}


/**
* The Callback class when calling the querySong(...) method
*/
private class SongCallback(val retFuture: CompletableFuture<String>): Callback{
override fun onResponse(call: Call, response: Response) {
retFuture.complete(response.body?.string())
}

override fun onFailure(call: Call, e: IOException) {
retFuture.completeExceptionally(e)
}
}


}


}
65 changes: 65 additions & 0 deletions app/src/main/java/ch/sdp/vibester/model/Song.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package ch.sdp.vibester.model

import org.json.JSONObject
import java.lang.IllegalArgumentException

/**
* A class representing a song
* @param jsonMeta a String that will be parsed
*/
class Song(jsonMeta: String) {

private var previewUrl = ""
private var artworkUrl = ""
private var trackName = ""
private var artistName = ""

// Here is the parsing from string (JSON) to retrieve the values
init {
try {
val jsonObj = JSONObject(jsonMeta)
val jsonArray = jsonObj.getJSONArray("results")
val jsonRes = jsonArray.getJSONObject(0)

previewUrl = jsonRes.getString("previewUrl")
artworkUrl = jsonRes.getString("artworkUrl100")
trackName = jsonRes.getString("trackName")
artistName = jsonRes.getString("artistName")
} catch(e: Exception){
throw IllegalArgumentException("Song constructor, bad argument")
}
}

/**
* Getter that return the previewUrl
* @return String that points towards an audio stream
*/
fun getPreviewUrl():String{
return previewUrl
}

/**
* Getter that return the artworkUrl
* @return String that points towards the artwork
*/
fun getArtworkUrl():String{
return artworkUrl
}

/**
* Getter that return the track name
* @return String containing the track name
*/
fun getTrackName():String{
return trackName
}

/**
* Getter that return the artist name
* @return String containing the artist name
*/
fun getArtistName():String{
return artistName
}

}
Loading