diff --git a/app/build.gradle b/app/build.gradle
index 91a85df..dfa7db0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -37,14 +37,17 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'com.google.android.material:material:1.2.1'
- implementation 'com.google.code.gson:gson:2.8.6'
+
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
implementation 'androidx.navigation:navigation-fragment:2.3.0'
implementation 'androidx.navigation:navigation-ui:2.3.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
+ implementation 'androidx.security:security-crypto:1.1.0-alpha02'
+
+ implementation 'com.google.android.material:material:1.2.1'
+ implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.github.bumptech.glide:glide:4.11.0'
@@ -52,6 +55,11 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
+ implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
+ implementation 'com.google.android.exoplayer:exoplayer-hls:2.12.0'
+ implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
+ implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'
+
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3a11a13..86d309e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
+
diff --git a/app/src/main/java/org/mosad/teapod/MainActivity.kt b/app/src/main/java/org/mosad/teapod/MainActivity.kt
index 502230e..0be7892 100644
--- a/app/src/main/java/org/mosad/teapod/MainActivity.kt
+++ b/app/src/main/java/org/mosad/teapod/MainActivity.kt
@@ -1,6 +1,8 @@
package org.mosad.teapod
+import android.content.Intent
import android.os.Bundle
+import android.util.Log
import android.view.MenuItem
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
@@ -8,6 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import kotlinx.android.synthetic.main.activity_main.*
import org.mosad.teapod.parser.AoDParser
+import org.mosad.teapod.preferences.EncryptedPreferences
import org.mosad.teapod.ui.MediaFragment
import org.mosad.teapod.ui.account.AccountFragment
import org.mosad.teapod.ui.home.HomeFragment
@@ -69,7 +72,14 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
}
private fun load() {
- // TODO
+ EncryptedPreferences.readCredentials(this)
+
+ if (EncryptedPreferences.password.isEmpty()) {
+ Log.i(javaClass.name, "please login!")
+
+ // TODO show login dialog
+ //EncryptedPreferences.saveCredentials("", "", this)
+ }
}
fun showDetailFragment(media: GUIMedia) {
@@ -85,4 +95,11 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
println("visible !!!: " + mediaFragment.isVisible)
println(supportFragmentManager.backStackEntryCount)
}
+
+ fun startPlayer(streamUrl: String) {
+ val intent = Intent(this, PlayerActivity::class.java).apply {
+ putExtra("streamUrl", streamUrl)
+ }
+ startActivity(intent)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/teapod/PlayerActivity.kt b/app/src/main/java/org/mosad/teapod/PlayerActivity.kt
new file mode 100644
index 0000000..19e317f
--- /dev/null
+++ b/app/src/main/java/org/mosad/teapod/PlayerActivity.kt
@@ -0,0 +1,107 @@
+package org.mosad.teapod
+
+import android.net.Uri
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import com.google.android.exoplayer2.ExoPlayer
+import com.google.android.exoplayer2.MediaItem
+import com.google.android.exoplayer2.Player
+import com.google.android.exoplayer2.SimpleExoPlayer
+import com.google.android.exoplayer2.source.hls.HlsMediaSource
+import com.google.android.exoplayer2.upstream.DataSource
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
+import com.google.android.exoplayer2.util.Util
+import kotlinx.android.synthetic.main.activity_player.*
+
+
+class PlayerActivity : AppCompatActivity() {
+
+ private lateinit var player: SimpleExoPlayer
+ private lateinit var dataSourceFactory: DataSource.Factory
+
+ private var streamUrl = ""
+
+ private var playWhenReady = true
+ private var currentWindow = 0
+ private var playbackPosition: Long = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_player)
+
+ streamUrl = intent.getStringExtra("streamUrl").toString()
+ }
+
+
+ override fun onStart() {
+ super.onStart()
+ if (Util.SDK_INT > 23) {
+ initPlayer()
+ if (video_view != null) video_view.onResume()
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (Util.SDK_INT <= 23) {
+ initPlayer()
+ if (video_view != null) video_view.onResume()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ if (Util.SDK_INT <= 23) {
+ if (video_view != null) video_view.onPause()
+ releasePlayer()
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+ if (Util.SDK_INT > 23) {
+ if (video_view != null) video_view.onPause()
+ releasePlayer()
+ }
+ }
+
+ private fun initPlayer() {
+ if (streamUrl.isEmpty()) {
+ println("no streamUrl set")
+ return
+ }
+
+ player = SimpleExoPlayer.Builder(this).build()
+ dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, "Teapod"))
+
+ val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
+ .createMediaSource(MediaItem.fromUri(Uri.parse(streamUrl)))
+
+ player.setMediaSource(mediaSource)
+ player.prepare()
+ player.play()
+
+ player.addListener(object : Player.EventListener {
+ override fun onPlaybackStateChanged(state: Int) {
+ super.onPlaybackStateChanged(state)
+
+ loading.visibility = when (state) {
+ ExoPlayer.STATE_READY -> View.GONE
+ ExoPlayer.STATE_BUFFERING -> View.VISIBLE
+ else -> View.GONE
+ }
+ }
+ })
+
+ video_view.player = player
+ }
+
+ private fun releasePlayer(){
+ playbackPosition = player.currentPosition
+ currentWindow = player.currentWindowIndex
+ playWhenReady = player.playWhenReady
+ player.release()
+ }
+
+}
diff --git a/app/src/main/java/org/mosad/teapod/dummy/DummyContent.kt b/app/src/main/java/org/mosad/teapod/dummy/DummyContent.kt
deleted file mode 100644
index 0707517..0000000
--- a/app/src/main/java/org/mosad/teapod/dummy/DummyContent.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.mosad.teapod.dummy
-
-import java.util.ArrayList
-import java.util.HashMap
-
-/**
- * Helper class for providing sample content for user interfaces created by
- * Android template wizards.
- *
- * TODO: Replace all uses of this class before publishing your app.
- */
-object DummyContent {
-
- /**
- * An array of sample (dummy) items.
- */
- val ITEMS: MutableList = ArrayList()
-
- /**
- * A map of sample (dummy) items, by ID.
- */
- val ITEM_MAP: MutableMap = HashMap()
-
- private val COUNT = 25
-
- init {
- // Add some sample items.
- for (i in 1..COUNT) {
- addItem(createDummyItem(i))
- }
- }
-
- private fun addItem(item: DummyItem) {
- ITEMS.add(item)
- ITEM_MAP.put(item.id, item)
- }
-
- private fun createDummyItem(position: Int): DummyItem {
- return DummyItem(position.toString(), "Item " + position, makeDetails(position))
- }
-
- private fun makeDetails(position: Int): String {
- val builder = StringBuilder()
- builder.append("Details about Item: ").append(position)
- for (i in 0..position - 1) {
- builder.append("\nMore details information here.")
- }
- return builder.toString()
- }
-
- /**
- * A dummy item representing a piece of content.
- */
- data class DummyItem(val id: String, val content: String, val details: String) {
- override fun toString(): String = content
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt
index 680751b..705ceeb 100644
--- a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt
+++ b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt
@@ -4,16 +4,14 @@ import com.google.gson.JsonParser
import kotlinx.coroutines.*
import org.jsoup.Connection
import org.jsoup.Jsoup
+import org.mosad.teapod.preferences.EncryptedPreferences
import org.mosad.teapod.util.GUIMedia
class AoDParser {
private val baseURL = "https://www.anime-on-demand.de"
private val loginPath = "/users/sign_in"
-
- // TODO
- private val login = ""
- private val pwd = ""
+ private val libraryPath = "/animes"
companion object {
private var sessionCookies = mutableMapOf()
@@ -39,8 +37,8 @@ class AoDParser {
println("cookies: $cookies")
val data = mapOf(
- Pair("user[login]", login),
- Pair("user[password]", pwd),
+ Pair("user[login]", EncryptedPreferences.login),
+ Pair("user[password]", EncryptedPreferences.password),
Pair("user[remember_me]", "1"),
Pair("commit", "Einloggen"),
Pair("authenticity_token", authenticityToken)
@@ -67,7 +65,7 @@ class AoDParser {
if (sessionCookies.isEmpty()) login()
withContext(Dispatchers.Default) {
- val resAnimes = Jsoup.connect("$baseURL/animes")
+ val resAnimes = Jsoup.connect(baseURL + libraryPath)
.cookies(sessionCookies)
.get()
@@ -147,7 +145,7 @@ class AoDParser {
.get("sources").asJsonArray
return@withContext sources.toList().map {
- it.asJsonObject.get("file").toString()
+ it.asJsonObject.get("file").asString
}
}
}
diff --git a/app/src/main/java/org/mosad/teapod/preferences/EncryptedPreferences.kt b/app/src/main/java/org/mosad/teapod/preferences/EncryptedPreferences.kt
new file mode 100644
index 0000000..de4c5ed
--- /dev/null
+++ b/app/src/main/java/org/mosad/teapod/preferences/EncryptedPreferences.kt
@@ -0,0 +1,66 @@
+package org.mosad.teapod.preferences
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.security.keystore.KeyGenParameterSpec
+import android.security.keystore.KeyProperties
+import android.util.Log
+import androidx.security.crypto.EncryptedSharedPreferences
+import androidx.security.crypto.MasterKey
+import org.mosad.teapod.R
+
+object EncryptedPreferences {
+
+ var login = ""
+ internal set
+ var password = ""
+ internal set
+
+ fun saveCredentials(login: String, password: String, context: Context) {
+ this.login = login
+ this.password = password
+
+ with(getEncryptedPreferences(context)?.edit()) {
+ this?.putString(context.getString(R.string.save_key_user_login), login)
+ this?.putString(context.getString(R.string.save_key_user_password), password)
+ this?.apply()
+ }
+ }
+
+ fun readCredentials(context: Context) {
+ with(getEncryptedPreferences(context)) {
+ login = this?.getString(context.getString(R.string.save_key_user_login), "").toString()
+ password = this?.getString(context.getString(R.string.save_key_user_password), "").toString()
+ }
+ }
+
+ /**
+ * create a encrypted shared preference
+ */
+ private fun getEncryptedPreferences(context: Context): SharedPreferences? {
+ return try {
+ val spec = KeyGenParameterSpec.Builder(
+ MasterKey.DEFAULT_MASTER_KEY_ALIAS,
+ KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setKeySize(MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE)
+ .build()
+
+ val masterKey = MasterKey.Builder(context)
+ .setKeyGenParameterSpec(spec)
+ .build()
+
+ EncryptedSharedPreferences.create(
+ context,
+ context.getString(R.string.encrypted_preference_file_key),
+ masterKey,
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
+ )
+ } catch (ex: Exception) {
+ Log.e(javaClass.name, "Could not create encrypted shared preference.", ex)
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/mosad/teapod/ui/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/ui/MediaFragment.kt
index 8f7bdbc..8fba103 100644
--- a/app/src/main/java/org/mosad/teapod/ui/MediaFragment.kt
+++ b/app/src/main/java/org/mosad/teapod/ui/MediaFragment.kt
@@ -7,8 +7,11 @@ import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.fragment_media.*
+import org.mosad.teapod.MainActivity
import org.mosad.teapod.R
import org.mosad.teapod.util.GUIMedia
+import java.net.URL
+import java.net.URLEncoder
class MediaFragment(val media: GUIMedia, val streams: List) : Fragment() {
@@ -36,7 +39,8 @@ class MediaFragment(val media: GUIMedia, val streams: List) : Fragment()
private fun onClickButtonPlay() {
println("play ${streams.first()}")
-
+ val mainActivity = activity as MainActivity
+ mainActivity.startPlayer(streams.first())
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_player.xml b/app/src/main/res/layout/activity_player.xml
new file mode 100644
index 0000000..f214940
--- /dev/null
+++ b/app/src/main/res/layout/activity_player.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 585dd9b..e5789b3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -4,4 +4,9 @@
Library
Search
Account
+
+
+ org.mosad.teapod.encrypted_preferences
+ org.mosad.teapod.user_login
+ org.mosad.teapod.user_password
\ No newline at end of file