Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import com.bumptech.glide.request.target.CustomTarget
import tech.salroid.filmy.ui.full.YoutubePlayerActivity.Companion.IS_SHORT
import tech.salroid.filmy.ui.full.YoutubePlayerActivity.Companion.VIDEO_ID
import tech.salroid.filmy.ui.full.YoutubePlayerActivity.Companion.VIDEO_TITLE
import tech.salroid.filmy.utility.isYoutubeShortVideo

@AndroidEntryPoint
class MovieDetailsActivity : AppCompatActivity() {
Expand Down Expand Up @@ -267,13 +269,17 @@ class MovieDetailsActivity : AppCompatActivity() {

binding.trailerView.setOnClickListener {
trailerFinal?.let {
Intent(
this@MovieDetailsActivity,
YoutubePlayerActivity::class.java
).run {
putExtra(VIDEO_ID, trailerFinal)
putExtra(VIDEO_TITLE, trailerTitle)
startActivity(this)
lifecycleScope.launch {
val isShort = isYoutubeShortVideo(it)
Intent(
this@MovieDetailsActivity,
YoutubePlayerActivity::class.java
).run {
putExtra(VIDEO_ID, trailerFinal)
putExtra(VIDEO_TITLE, trailerTitle)
putExtra(IS_SHORT, isShort)
startActivity(this)
}
}
Comment on lines +272 to 283
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The network call to isYoutubeShortVideo is launched in a coroutine without error handling. If the network call fails or throws an exception, the coroutine will crash and the Intent with the activity will never be started, leaving the user with no feedback. The user would click on the trailer and nothing would happen. Consider wrapping the call in a try-catch block and providing fallback behavior (e.g., launch the video with isShort=false as default) or showing an error message to the user.

Copilot uses AI. Check for mistakes.
}
}
Expand Down Expand Up @@ -701,4 +707,4 @@ class MovieDetailsActivity : AppCompatActivity() {
startActivity(Intent.createChooser(myIntent, "Share with"))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import android.view.*
import android.view.animation.DecelerateInterpolator
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import kotlinx.coroutines.launch
import tech.salroid.filmy.R
import tech.salroid.filmy.data.local.model.TrailerData
import tech.salroid.filmy.databinding.AllTrailerLayoutBinding
import tech.salroid.filmy.ui.adapters.MovieTrailersAdapter
import tech.salroid.filmy.ui.full.YoutubePlayerActivity.Companion.IS_SHORT
import tech.salroid.filmy.utility.isYoutubeShortVideo
import tech.salroid.filmy.utility.themeSystemBars
import kotlin.math.hypot
import androidx.core.graphics.toColorInt
Expand Down Expand Up @@ -130,10 +134,14 @@ class AllTrailersFragment : Fragment() {
}

private fun playTrailerOnYoutube(trailerId: String, trailerTitle: String?) {
Intent(activity, YoutubePlayerActivity::class.java).run {
putExtra(YoutubePlayerActivity.VIDEO_ID, trailerId)
putExtra(YoutubePlayerActivity.VIDEO_TITLE, trailerTitle)
startActivity(this)
lifecycleScope.launch {
val isShort = isYoutubeShortVideo(trailerId)
Intent(activity, YoutubePlayerActivity::class.java).run {
putExtra(YoutubePlayerActivity.VIDEO_ID, trailerId)
putExtra(YoutubePlayerActivity.VIDEO_TITLE, trailerTitle)
putExtra(IS_SHORT, isShort)
startActivity(this)
}
}
Comment on lines +137 to 145
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The network call to isYoutubeShortVideo is launched in a coroutine without error handling. If the network call fails or throws an exception, the coroutine will crash and the Intent with the activity will never be started, leaving the user with no feedback. The user would click on a trailer and nothing would happen. Consider wrapping the call in a try-catch block and providing fallback behavior (e.g., launch the video with isShort=false as default) or showing an error message to the user.

Copilot uses AI. Check for mistakes.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ package tech.salroid.filmy.ui.full

import android.annotation.SuppressLint
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.view.HapticFeedbackConstants.VIRTUAL_KEY
import android.view.View
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
Expand All @@ -25,22 +28,28 @@ class YoutubePlayerActivity : AppCompatActivity() {

private var videoId: String? = null
private var videoTitle: String? = null
private var isShort: Boolean = false
private var html5VideoView: View? = null
private var customViewCallback: WebChromeClient.CustomViewCallback? = null

companion object {
const val VIDEO_ID = "video_id"
const val VIDEO_TITLE = "video_title"
private const val YT_BASE_URL = "https://www.youtube.com"
const val IS_SHORT = "is_short"
private const val APP_BASE_URL = "https://app.filmy.tech"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'origin' parameter in the YouTube iframe player configuration is set to 'https://app.filmy.tech', and the base URL for loadDataWithBaseURL is also changed from 'https://www.youtube.com' to 'https://app.filmy.tech'. However, it's unclear if 'app.filmy.tech' is actually configured and owned by the app developers. If this domain doesn't exist or isn't properly configured, the YouTube iframe API might reject requests or fail to initialize properly. Please verify that this domain is valid and properly configured for the app's use case.

Suggested change
private const val APP_BASE_URL = "https://app.filmy.tech"
private const val APP_BASE_URL = "https://www.youtube.com"

Copilot uses AI. Check for mistakes.
private const val MIME_TYPE = "text/html"
private const val ENCODING = "utf-8"
private const val DESKTOP_USER_AGENT =
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

videoId = intent.getStringExtra(VIDEO_ID)
videoTitle = intent.getStringExtra(VIDEO_TITLE)
isShort = intent.getBooleanExtra(IS_SHORT, false)

binding = ActivityFullScreenYoutubeBinding.inflate(layoutInflater)
setContentView(binding.root)
setupUI()
Expand All @@ -63,18 +72,20 @@ class YoutubePlayerActivity : AppCompatActivity() {
setBackgroundColor(Color.TRANSPARENT)
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mediaPlaybackRequiresUserGesture = false
setSupportZoom(false)
builtInZoomControls = false
displayZoomControls = false
userAgentString = DESKTOP_USER_AGENT
}

webViewClient = YTWebViewClient()
webChromeClient = YTWebChromeClient()

// Load the YouTube video iframe HTML
loadDataWithBaseURL(
YT_BASE_URL,
APP_BASE_URL,
getYouTubeIframeHTML(id),
MIME_TYPE,
ENCODING,
Expand All @@ -89,17 +100,22 @@ class YoutubePlayerActivity : AppCompatActivity() {
view: WebView?,
request: WebResourceRequest?
): Boolean {
try {
view?.context?.startActivity(
android.content.Intent(
android.content.Intent.ACTION_VIEW,
request?.url
val url = request?.url.toString()
return if (url.startsWith("https://www.youtube.com")) {
false
} else {
try {
view?.context?.startActivity(
android.content.Intent(
android.content.Intent.ACTION_VIEW,
request?.url
)
)
)
} catch (e: Exception) {
e.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
true
}
return true
}
}

Expand All @@ -112,7 +128,26 @@ class YoutubePlayerActivity : AppCompatActivity() {
html5VideoView = view
customViewCallback = callback

requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE
if (isShort) {
requestedOrientation = SCREEN_ORIENTATION_PORTRAIT
val displayMetrics = resources.displayMetrics
val screenWidth = displayMetrics.widthPixels
val videoHeight = screenWidth * 16 / 9
Comment on lines +131 to +135
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aspect ratio calculation for shorts appears to be incorrect. YouTube Shorts are vertical videos with a 9:16 aspect ratio, but this code calculates videoHeight = screenWidth * 16 / 9, which gives a 16:9 (landscape) aspect ratio. This would result in the video being displayed in the wrong dimensions for portrait short videos. The calculation should be videoHeight = screenWidth * 16 / 9 for landscape videos, but for shorts it should match the device height or use a different calculation appropriate for vertical content.

Copilot uses AI. Check for mistakes.

val layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
videoHeight,
Gravity.CENTER
)
view?.layoutParams = layoutParams
} else {
requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE
val layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
view?.layoutParams = layoutParams
}

binding.headerContainer.visibility = View.GONE
binding.webviewContainer.visibility = View.GONE
Expand Down Expand Up @@ -161,4 +196,4 @@ class YoutubePlayerActivity : AppCompatActivity() {
binding.youtubeWebView.destroy()
super.onDestroy()
}
}
}
31 changes: 27 additions & 4 deletions app/src/main/java/tech/salroid/filmy/utility/YoutubeUtils.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package tech.salroid.filmy.utility

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request

/**
* Generates the HTML string required to embed a YouTube video using an iframe.
Expand All @@ -25,10 +29,10 @@ fun getYouTubeIframeHTML(youtubeVideoId: String): String {
'controls': 1,
'fs': 1,
'rel': 0,
'modestbranding': 1,
'iv_load_policy': 3,
'showinfo': 0,
'playsinline': 1
'playsinline': 1,
'origin': 'https://app.filmy.tech',
'enablejsapi': 1
},
events: {
'onReady': function(event) { event.target.playVideo(); }
Expand All @@ -43,4 +47,23 @@ fun getYouTubeIframeHTML(youtubeVideoId: String): String {
</body>
</html>
""".trimIndent()
}
}

private val client = OkHttpClient()
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OkHttpClient instance is created without any timeout configurations. This means network requests could hang indefinitely if YouTube's server doesn't respond. This is particularly problematic since this network call is made synchronously before launching the video player, potentially blocking the UI thread from completing the activity launch. Consider configuring timeouts (connect, read, and write) for the OkHttpClient to prevent indefinite hangs.

Copilot uses AI. Check for mistakes.

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added isYoutubeShortVideo function lacks documentation. The existing getYouTubeIframeHTML function has KDoc documentation explaining its purpose, parameters, and return value. For consistency with the codebase convention (see line 8-13 in the same file), this function should also have KDoc documentation explaining what it does, what the videoId parameter represents, what the return value means, and potentially noting that it makes a network request.

Suggested change
/**
* Checks whether the given YouTube video is likely a YouTube Short.
*
* This function performs an HTTP request to the standard watch URL for the
* provided video ID and inspects the response to infer if it corresponds to
* a Shorts video.
*
* This is a suspending function that executes the network request on
* [Dispatchers.IO].
*
* @param videoId The unique identifier of the YouTube video to inspect.
* @return `true` if the video appears to be a YouTube Short, `false` otherwise
* or if the request fails.
*/

Copilot uses AI. Check for mistakes.
suspend fun isYoutubeShortVideo(videoId: String): Boolean =
Comment on lines +52 to +54
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OkHttpClient is created as a module-level private property, which means a new instance is created for each process. While this works, it doesn't follow the recommended practice of sharing a single OkHttpClient instance across the app. The codebase already has a centralized OkHttpClient provided through dependency injection in MoviesModule.kt (line 24). Consider using dependency injection to provide this OkHttpClient instance or reusing the existing injected client to maintain consistency and improve resource efficiency (connection pooling, thread pools, etc.).

Suggested change
private val client = OkHttpClient()
suspend fun isYoutubeShortVideo(videoId: String): Boolean =
suspend fun isYoutubeShortVideo(videoId: String, client: OkHttpClient): Boolean =

Copilot uses AI. Check for mistakes.
withContext(Dispatchers.IO) {
val url = "https://www.youtube.com/watch?v=$videoId"

val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
Comment on lines +52 to +59
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTTP request to YouTube is made over HTTPS, but there's no validation of the SSL certificate or additional security measures in the OkHttpClient configuration. While this is generally acceptable for fetching public content, it's worth noting that the OkHttpClient could be configured with certificate pinning or other security measures if needed, especially since the app appears to handle sensitive user data (accounts, favorites, etc.). This is more of a consideration than a critical issue.

Copilot uses AI. Check for mistakes.
if (!response.isSuccessful) return@withContext false

val body = response.body?.string() ?: return@withContext false

val shortsPattern = Regex("""href=["'](https://www\.youtube\.com/shorts/[^"']+)["']""")
val match = shortsPattern.find(body)

return@withContext match != null
Comment on lines +64 to +67
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach of scraping YouTube's HTML page to detect shorts is fragile and may break if YouTube changes their HTML structure. YouTube can change their page markup at any time without notice, which would cause this detection to fail silently (always returning false). Consider using YouTube's official Data API v3 which provides a more reliable and stable way to get video information, or document this as a known limitation with fallback behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +67
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isYoutubeShortVideo function lacks error handling for network failures and exceptions. If the network call throws an exception (e.g., timeout, network unavailable, malformed URL), it will crash or propagate the exception to the caller. This could cause the app to fail when trying to play a YouTube video. Consider wrapping the network call in a try-catch block and returning a default value (false) in case of errors, so the video playback can proceed with the default non-short handling.

Suggested change
val url = "https://www.youtube.com/watch?v=$videoId"
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) return@withContext false
val body = response.body?.string() ?: return@withContext false
val shortsPattern = Regex("""href=["'](https://www\.youtube\.com/shorts/[^"']+)["']""")
val match = shortsPattern.find(body)
return@withContext match != null
try {
val url = "https://www.youtube.com/watch?v=$videoId"
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
return@use false
}
val body = response.body?.string() ?: return@use false
val shortsPattern = Regex("""href=["'](https://www\.youtube\.com/shorts/[^"']+)["']""")
val match = shortsPattern.find(body)
match != null
}
} catch (e: Exception) {
false

Copilot uses AI. Check for mistakes.
}
}
Comment on lines +54 to +69
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isYoutubeShortVideo function makes an HTTP request to fetch the entire YouTube page HTML just to check if it's a short video. This is inefficient and wasteful, as it downloads potentially large HTML content for a simple check. Additionally, there's no caching mechanism, so repeated calls for the same video ID will make redundant network requests. Consider using YouTube's Data API v3 instead (the app already has a YOUTUBE_API_KEY configured), or at least implement caching to avoid repeated network calls for the same video ID.

Copilot uses AI. Check for mistakes.