add player
* fix aod stream parsing
This commit is contained in:
parent
818981190d
commit
2866d01c22
@ -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'
|
||||
|
@ -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">
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
107
app/src/main/java/org/mosad/teapod/PlayerActivity.kt
Normal file
107
app/src/main/java/org/mosad/teapod/PlayerActivity.kt
Normal 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()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
||||
}
|
23
app/src/main/res/layout/activity_player.xml
Normal file
23
app/src/main/res/layout/activity_player.xml
Normal 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>
|
@ -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>
|
Loading…
Reference in New Issue
Block a user