mirror of
https://github.com/lizongying/my-tv.git
synced 2026-11-03 18:16:29 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
188431a29f | ||
|
|
5c0152e36c | ||
|
|
a63463559f |
@@ -230,6 +230,7 @@ adb install my-tv.apk
|
|||||||
* 节目增加预告
|
* 节目增加预告
|
||||||
* 频道列表优化
|
* 频道列表优化
|
||||||
* epg更新后菜单更新
|
* epg更新后菜单更新
|
||||||
|
* 防止菜单第一个获取焦点
|
||||||
|
|
||||||
## 赞赏
|
## 赞赏
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -17,15 +17,15 @@ import com.lizongying.mytv.models.TVViewModel
|
|||||||
|
|
||||||
class CardAdapter(
|
class CardAdapter(
|
||||||
private val recyclerView: RecyclerView,
|
private val recyclerView: RecyclerView,
|
||||||
private val owner: LifecycleOwner,
|
private val mainFragment: MainFragment,
|
||||||
private var tvListViewModel: TVListViewModel,
|
private var tvListViewModel: TVListViewModel,
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
|
RecyclerView.Adapter<CardAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private var listener: ItemListener? = null
|
private var listener: ItemListener? = null
|
||||||
private var focused: View? = null
|
private var focused: View? = null
|
||||||
private var defaultFocused = false
|
|
||||||
var defaultFocus: Int = -1
|
var visiable = false
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
val cardView = object :
|
val cardView = object :
|
||||||
@@ -57,19 +57,23 @@ class CardAdapter(
|
|||||||
|
|
||||||
val tvViewModel = item as TVViewModel
|
val tvViewModel = item as TVViewModel
|
||||||
val cardView = viewHolder.view as ImageCardView
|
val cardView = viewHolder.view as ImageCardView
|
||||||
|
cardView.tag = tvViewModel
|
||||||
|
|
||||||
if (!defaultFocused && item.id.value == defaultFocus) {
|
val onFocusChangeListener = View.OnFocusChangeListener { view, hasFocus ->
|
||||||
cardView.requestFocus()
|
|
||||||
defaultFocused = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
|
|
||||||
listener?.onItemFocusChange(item, hasFocus)
|
|
||||||
|
|
||||||
// if (hasFocus && defaultFocus == item.id.value) {
|
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
|
listener?.onItemHasFocus(item)
|
||||||
focused = cardView
|
focused = cardView
|
||||||
|
|
||||||
startScaleAnimation(cardView, 0.9f, 1.0f, 200)
|
startScaleAnimation(cardView, 0.9f, 1.0f, 200)
|
||||||
|
//
|
||||||
|
// if (mainFragment.shouldHasFocus(view.tag as TVViewModel)) {
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (visiable) {
|
||||||
|
// startScaleAnimation(cardView, 0.9f, 1.0f, 200)
|
||||||
|
// } else {
|
||||||
|
// visiable = true
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,8 +114,16 @@ class CardAdapter(
|
|||||||
val view = itemView
|
val view = itemView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toPosition(position: Int) {
|
||||||
|
recyclerView.post {
|
||||||
|
recyclerView.scrollToPosition(position)
|
||||||
|
recyclerView.getChildAt(position)?.isSelected
|
||||||
|
recyclerView.getChildAt(position)?.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface ItemListener {
|
interface ItemListener {
|
||||||
fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean)
|
fun onItemHasFocus(tvViewModel: TVViewModel)
|
||||||
fun onItemClicked(tvViewModel: TVViewModel)
|
fun onItemClicked(tvViewModel: TVViewModel)
|
||||||
fun onKey(keyCode: Int): Boolean
|
fun onKey(keyCode: Int): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
val adapter =
|
val adapter =
|
||||||
CardAdapter(
|
CardAdapter(
|
||||||
itemBinding.rowItems,
|
itemBinding.rowItems,
|
||||||
viewLifecycleOwner,
|
this,
|
||||||
tvListViewModelCurrent,
|
tvListViewModelCurrent,
|
||||||
)
|
)
|
||||||
rowList.add(itemBinding.rowItems)
|
rowList.add(itemBinding.rowItems)
|
||||||
@@ -111,14 +111,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
itemPosition = 0
|
itemPosition = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val tv = tvListViewModel.getTVViewModel(itemPosition)
|
|
||||||
for (i in rowList) {
|
|
||||||
if (i.tag as Int == tv?.getRowPosition()) {
|
|
||||||
((i as RecyclerView).adapter as CardAdapter).defaultFocus = itemPosition
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tvListViewModel.setItemPosition(itemPosition)
|
tvListViewModel.setItemPosition(itemPosition)
|
||||||
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
|
tvListViewModel.tvListViewModel.value?.forEach { tvViewModel ->
|
||||||
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
|
tvViewModel.errInfo.observe(viewLifecycleOwner) { _ ->
|
||||||
@@ -191,20 +183,18 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemFocusChange(tvViewModel: TVViewModel, hasFocus: Boolean) {
|
override fun onItemHasFocus(tvViewModel: TVViewModel) {
|
||||||
if (hasFocus) {
|
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!)
|
||||||
tvListViewModel.setItemPositionCurrent(tvViewModel.id.value!!)
|
|
||||||
|
|
||||||
val row = tvViewModel.getRowPosition()
|
val row = tvViewModel.getRowPosition()
|
||||||
|
|
||||||
for (i in rowList) {
|
for (i in rowList) {
|
||||||
if (i.tag as Int != row) {
|
if (i.tag as Int != row) {
|
||||||
((i as RecyclerView).adapter as CardAdapter).clear()
|
((i as RecyclerView).adapter as CardAdapter).clear()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(activity as MainActivity).mainActive()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(activity as MainActivity).mainActive()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClicked(tvViewModel: TVViewModel) {
|
override fun onItemClicked(tvViewModel: TVViewModel) {
|
||||||
@@ -237,8 +227,6 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
itemPosition
|
itemPosition
|
||||||
)?.requestFocus()
|
)?.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
((rowList[rowPosition] as RecyclerView).adapter as CardAdapter).defaultFocus = itemPosition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun check(tvViewModel: TVViewModel): Boolean {
|
fun check(tvViewModel: TVViewModel): Boolean {
|
||||||
@@ -304,6 +292,32 @@ class MainFragment : Fragment(), CardAdapter.ItemListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun shouldHasFocus(tvModel: TVViewModel):Boolean {
|
||||||
|
return tvModel == tvListViewModel.getTVViewModel(itemPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onHiddenChanged(hidden: Boolean) {
|
||||||
|
super.onHiddenChanged(hidden)
|
||||||
|
if (!hidden) {
|
||||||
|
val tvModel = tvListViewModel.getTVViewModel(itemPosition)
|
||||||
|
val rowPosition = tvModel?.getRowPosition()
|
||||||
|
val itemPosition = tvModel?.getItemPosition()
|
||||||
|
Log.i(TAG, "toPosition $rowPosition $itemPosition")
|
||||||
|
for (i in rowList) {
|
||||||
|
if (i.tag as Int == rowPosition) {
|
||||||
|
((i as RecyclerView).adapter as CardAdapter).toPosition(itemPosition!!)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view?.post {
|
||||||
|
// for (i in rowList) {
|
||||||
|
// ((i as RecyclerView).adapter as CardAdapter).visiable = false
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
Log.i(TAG, "onResume")
|
Log.i(TAG, "onResume")
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ object Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fetchVideo(tvModel: TVViewModel, cookie: String) {
|
fun fetchVideo(tvModel: TVViewModel, cookie: String) {
|
||||||
|
Log.i(TAG, "fetchVideo with cookie")
|
||||||
call?.cancel()
|
call?.cancel()
|
||||||
if (::btraceRunnable.isInitialized) {
|
if (::btraceRunnable.isInitialized) {
|
||||||
handler.removeCallbacks(btraceRunnable)
|
handler.removeCallbacks(btraceRunnable)
|
||||||
@@ -399,9 +400,9 @@ object Request {
|
|||||||
// Log.d(TAG, "$title url $url")
|
// Log.d(TAG, "$title url $url")
|
||||||
tvModel.addVideoUrl(url)
|
tvModel.addVideoUrl(url)
|
||||||
tvModel.allReady()
|
tvModel.allReady()
|
||||||
tvModel.retryTimes = 0
|
tvModel.tokenRetryTimes = 0
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "auth status error")
|
Log.e(TAG, "auth status error ${response.code()}")
|
||||||
if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) {
|
if (tvModel.tokenRetryTimes < tvModel.tokenRetryMaxTimes) {
|
||||||
tvModel.tokenRetryTimes++
|
tvModel.tokenRetryTimes++
|
||||||
fetchFAuth(tvModel)
|
fetchFAuth(tvModel)
|
||||||
@@ -420,6 +421,7 @@ object Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fetchData(tvModel: TVViewModel) {
|
fun fetchData(tvModel: TVViewModel) {
|
||||||
|
Log.i(TAG, "fetchData")
|
||||||
if (tvModel.getTV().channel == "港澳台") {
|
if (tvModel.getTV().channel == "港澳台") {
|
||||||
fetchFAuth(tvModel)
|
fetchFAuth(tvModel)
|
||||||
return
|
return
|
||||||
@@ -445,7 +447,6 @@ object Request {
|
|||||||
class TokenRunnable : Runnable {
|
class TokenRunnable : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
fetchToken()
|
fetchToken()
|
||||||
handler.postDelayed(this, 600000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.lizongying.mytv.api
|
package com.lizongying.mytv.api
|
||||||
|
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import okhttp3.ConnectionSpec
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.TlsVersion
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import retrofit2.converter.protobuf.ProtoConverterFactory
|
import retrofit2.converter.protobuf.ProtoConverterFactory
|
||||||
@@ -68,6 +72,36 @@ class ApiClient {
|
|||||||
.build().create(FAuthService::class.java)
|
.build().create(FAuthService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun enableTls12OnPreLollipop(client: OkHttpClient.Builder): OkHttpClient.Builder {
|
||||||
|
if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 22) {
|
||||||
|
try {
|
||||||
|
val sc = SSLContext.getInstance("TLSv1.2")
|
||||||
|
|
||||||
|
sc.init(null, null, null)
|
||||||
|
|
||||||
|
// a more robust version is to pass a custom X509TrustManager
|
||||||
|
// as the second parameter and make checkServerTrusted to accept your server.
|
||||||
|
// Credits: https://github.com/square/okhttp/issues/2372#issuecomment-1774955225
|
||||||
|
client.sslSocketFactory(Tls12SocketFactory(sc.socketFactory))
|
||||||
|
|
||||||
|
val cs = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||||
|
.tlsVersions(TlsVersion.TLS_1_2)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val specs: MutableList<ConnectionSpec> = ArrayList()
|
||||||
|
specs.add(cs)
|
||||||
|
specs.add(ConnectionSpec.COMPATIBLE_TLS)
|
||||||
|
specs.add(ConnectionSpec.CLEARTEXT)
|
||||||
|
|
||||||
|
client.connectionSpecs(specs)
|
||||||
|
} catch (exc: java.lang.Exception) {
|
||||||
|
Log.e("OkHttpTLSCompat", "Error while setting TLS 1.2", exc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
private fun getUnsafeOkHttpClient(): OkHttpClient {
|
private fun getUnsafeOkHttpClient(): OkHttpClient {
|
||||||
try {
|
try {
|
||||||
val trustAllCerts: Array<TrustManager> = arrayOf(
|
val trustAllCerts: Array<TrustManager> = arrayOf(
|
||||||
@@ -93,11 +127,12 @@ class ApiClient {
|
|||||||
val sslContext = SSLContext.getInstance("SSL")
|
val sslContext = SSLContext.getInstance("SSL")
|
||||||
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
||||||
|
|
||||||
return OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
|
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
|
||||||
.hostnameVerifier { _, _ -> true }
|
.hostnameVerifier { _, _ -> true }
|
||||||
.dns(DnsCache())
|
.dns(DnsCache())
|
||||||
.build()
|
|
||||||
|
return enableTls12OnPreLollipop(builder).build()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.lizongying.mytv.api
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.Socket
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
import javax.net.ssl.SSLSocket
|
||||||
|
import javax.net.ssl.SSLSocketFactory
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables TLS v1.2 when creating SSLSockets.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* For some reason, android supports TLS v1.2 from API 16, but enables it by
|
||||||
|
* default only from API 20.
|
||||||
|
* @link https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
|
||||||
|
* @see SSLSocketFactory
|
||||||
|
*/
|
||||||
|
class Tls12SocketFactory(val delegate: SSLSocketFactory) : SSLSocketFactory() {
|
||||||
|
override fun getDefaultCipherSuites(): Array<String> {
|
||||||
|
return delegate.defaultCipherSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSupportedCipherSuites(): Array<String> {
|
||||||
|
return delegate.supportedCipherSuites
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
|
||||||
|
return patch(delegate.createSocket(s, host, port, autoClose))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, UnknownHostException::class)
|
||||||
|
override fun createSocket(host: String, port: Int): Socket {
|
||||||
|
return patch(delegate.createSocket(host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class, UnknownHostException::class)
|
||||||
|
override fun createSocket(
|
||||||
|
host: String,
|
||||||
|
port: Int,
|
||||||
|
localHost: InetAddress,
|
||||||
|
localPort: Int
|
||||||
|
): Socket {
|
||||||
|
return patch(delegate.createSocket(host, port, localHost, localPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun createSocket(host: InetAddress, port: Int): Socket {
|
||||||
|
return patch(delegate.createSocket(host, port))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun createSocket(
|
||||||
|
address: InetAddress,
|
||||||
|
port: Int,
|
||||||
|
localAddress: InetAddress,
|
||||||
|
localPort: Int
|
||||||
|
): Socket {
|
||||||
|
return patch(delegate.createSocket(address, port, localAddress, localPort))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun patch(s: Socket): Socket {
|
||||||
|
if (s is SSLSocket) {
|
||||||
|
s.enabledProtocols = TLS_V12_ONLY
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TLS_V12_ONLY = arrayOf("TLSv1.2")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user