add player

* fix aod stream parsing
This commit is contained in:
Jannik 2020-10-11 13:18:20 +02:00
parent 818981190d
commit 2866d01c22
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
10 changed files with 241 additions and 69 deletions

View File

@ -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'

View File

@ -11,6 +11,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".PlayerActivity"></activity>
<activity
android:name=".MainActivity"
android:label="@string/app_name">

View File

@ -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)
}
}

View File

@ -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()
}
}

View File

@ -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<DummyItem> = ArrayList()
/**
* A map of sample (dummy) items, by ID.
*/
val ITEM_MAP: MutableMap<String, DummyItem> = 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
}
}

View File

@ -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<String, String>()
@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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<String>) : Fragment() {
@ -36,7 +39,8 @@ class MediaFragment(val media: GUIMedia, val streams: List<String>) : Fragment()
private fun onClickButtonPlay() {
println("play ${streams.first()}")
val mainActivity = activity as MainActivity
mainActivity.startPlayer(streams.first())
}
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
tools:context=".PlayerActivity">
<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center" />
<!-- app:controller_layout_id="@layout/player_custom_control"/>-->
<ProgressBar
android:id="@+id/loading"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center" />
</FrameLayout>

View File

@ -4,4 +4,9 @@
<string name="title_library">Library</string>
<string name="title_search">Search</string>
<string name="title_account">Account</string>
<!-- save keys -->
<string name="encrypted_preference_file_key" translatable="false">org.mosad.teapod.encrypted_preferences</string>
<string name="save_key_user_login" translatable="false">org.mosad.teapod.user_login</string>
<string name="save_key_user_password" translatable="false">org.mosad.teapod.user_password</string>
</resources>