add player
* fix aod stream parsing
This commit is contained in:
		| @ -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> | ||||
		Reference in New Issue
	
	Block a user