forked from recloudstream/cloudstream
		
	Added support for embedded subtitles
This commit is contained in:
		
							parent
							
								
									b4b06b2389
								
							
						
					
					
						commit
						2d0e75b921
					
				
					 8 changed files with 105 additions and 16 deletions
				
			
		|  | @ -92,6 +92,10 @@ abstract class AbstractPlayerFragment( | ||||||
|         throw NotImplementedError() |         throw NotImplementedError() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     open fun embeddedSubtitlesFetched(subtitles: List<SubtitleData>) { | ||||||
|  |         throw NotImplementedError() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     open fun exitedPipMode() { |     open fun exitedPipMode() { | ||||||
|         throw NotImplementedError() |         throw NotImplementedError() | ||||||
|     } |     } | ||||||
|  | @ -338,7 +342,9 @@ abstract class AbstractPlayerFragment( | ||||||
|                 PRELOAD_NEXT_EPISODE_PERCENTAGE, |                 PRELOAD_NEXT_EPISODE_PERCENTAGE, | ||||||
|                 NEXT_WATCH_EPISODE_PERCENTAGE, |                 NEXT_WATCH_EPISODE_PERCENTAGE, | ||||||
|                 UPDATE_SYNC_PROGRESS_PERCENTAGE, |                 UPDATE_SYNC_PROGRESS_PERCENTAGE, | ||||||
|             ), subtitlesUpdates = ::subtitlesChanged |             ), | ||||||
|  |             subtitlesUpdates = ::subtitlesChanged, | ||||||
|  |             embeddedSubtitlesFetched = ::embeddedSubtitlesFetched, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         if (player is CS3IPlayer) { |         if (player is CS3IPlayer) { | ||||||
|  |  | ||||||
|  | @ -27,9 +27,11 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromName | ||||||
| import com.lagradost.cloudstream3.USER_AGENT | import com.lagradost.cloudstream3.USER_AGENT | ||||||
| import com.lagradost.cloudstream3.app | import com.lagradost.cloudstream3.app | ||||||
| import com.lagradost.cloudstream3.mvvm.logError | import com.lagradost.cloudstream3.mvvm.logError | ||||||
|  | import com.lagradost.cloudstream3.mvvm.normalSafeApiCall | ||||||
| import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle | import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorLink | import com.lagradost.cloudstream3.utils.ExtractorLink | ||||||
| import com.lagradost.cloudstream3.utils.ExtractorUri | import com.lagradost.cloudstream3.utils.ExtractorUri | ||||||
|  | import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage | ||||||
| import java.io.File | import java.io.File | ||||||
| import javax.net.ssl.HttpsURLConnection | import javax.net.ssl.HttpsURLConnection | ||||||
| import javax.net.ssl.SSLContext | import javax.net.ssl.SSLContext | ||||||
|  | @ -95,6 +97,7 @@ class CS3IPlayer : IPlayer { | ||||||
|     private var prevEpisode: (() -> Unit)? = null |     private var prevEpisode: (() -> Unit)? = null | ||||||
| 
 | 
 | ||||||
|     private var playerUpdated: ((Any?) -> Unit)? = null |     private var playerUpdated: ((Any?) -> Unit)? = null | ||||||
|  |     private var embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null | ||||||
| 
 | 
 | ||||||
|     override fun initCallbacks( |     override fun initCallbacks( | ||||||
|         playerUpdated: (Any?) -> Unit, |         playerUpdated: (Any?) -> Unit, | ||||||
|  | @ -107,6 +110,7 @@ class CS3IPlayer : IPlayer { | ||||||
|         nextEpisode: (() -> Unit)?, |         nextEpisode: (() -> Unit)?, | ||||||
|         prevEpisode: (() -> Unit)?, |         prevEpisode: (() -> Unit)?, | ||||||
|         subtitlesUpdates: (() -> Unit)?, |         subtitlesUpdates: (() -> Unit)?, | ||||||
|  |         embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)?, | ||||||
|     ) { |     ) { | ||||||
|         this.playerUpdated = playerUpdated |         this.playerUpdated = playerUpdated | ||||||
|         this.updateIsPlaying = updateIsPlaying |         this.updateIsPlaying = updateIsPlaying | ||||||
|  | @ -118,6 +122,7 @@ class CS3IPlayer : IPlayer { | ||||||
|         this.nextEpisode = nextEpisode |         this.nextEpisode = nextEpisode | ||||||
|         this.prevEpisode = prevEpisode |         this.prevEpisode = prevEpisode | ||||||
|         this.subtitlesUpdates = subtitlesUpdates |         this.subtitlesUpdates = subtitlesUpdates | ||||||
|  |         this.embeddedSubtitlesFetched = embeddedSubtitlesFetched | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // I know, this is not a perfect solution, however it works for fixing subs |     // I know, this is not a perfect solution, however it works for fixing subs | ||||||
|  | @ -202,7 +207,14 @@ class CS3IPlayer : IPlayer { | ||||||
| 
 | 
 | ||||||
|                         trackSelector.setParameters( |                         trackSelector.setParameters( | ||||||
|                             trackSelector.buildUponParameters() |                             trackSelector.buildUponParameters() | ||||||
|                                 .setPreferredTextLanguage("_$name") |                                 .apply { | ||||||
|  |                                     if (subtitle.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) | ||||||
|  |                                     // The real Language (two letter) is in the url | ||||||
|  |                                     // No underscore as the .url is the actual exoplayer designated language | ||||||
|  |                                         setPreferredTextLanguage(subtitle.url) | ||||||
|  |                                     else | ||||||
|  |                                         setPreferredTextLanguage("_$name") | ||||||
|  |                                 } | ||||||
|                         ) |                         ) | ||||||
| 
 | 
 | ||||||
|                         // ugliest code I have written, it seeks 1ms to *update* the subtitles |                         // ugliest code I have written, it seeks 1ms to *update* the subtitles | ||||||
|  | @ -237,10 +249,14 @@ class CS3IPlayer : IPlayer { | ||||||
|     override fun getCurrentPreferredSubtitle(): SubtitleData? { |     override fun getCurrentPreferredSubtitle(): SubtitleData? { | ||||||
|         return subtitleHelper.getAllSubtitles().firstOrNull { sub -> |         return subtitleHelper.getAllSubtitles().firstOrNull { sub -> | ||||||
|             exoPlayerSelectedTracks.any { |             exoPlayerSelectedTracks.any { | ||||||
|  |                 // When embedded the real language is in .url as the real name is a two letter code | ||||||
|  |                 val realName = | ||||||
|  |                     if (sub.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) sub.url else sub.name | ||||||
|  | 
 | ||||||
|                 // The replace is needed as exoplayer translates _ to - |                 // The replace is needed as exoplayer translates _ to - | ||||||
|                 // Also we prefix the languages with _ |                 // Also we prefix the languages with _ | ||||||
|                 it.second && it.first.replace("-", "").equals( |                 it.second && it.first.replace("-", "").equals( | ||||||
|                     sub.name.replace("-", ""), |                     realName.replace("-", ""), | ||||||
|                     ignoreCase = true |                     ignoreCase = true | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
|  | @ -616,9 +632,46 @@ class CS3IPlayer : IPlayer { | ||||||
|                  * Records the current used subtitle/track. Needed as exoplayer seems to have loose track language selection. |                  * Records the current used subtitle/track. Needed as exoplayer seems to have loose track language selection. | ||||||
|                  * */ |                  * */ | ||||||
|                 override fun onTracksInfoChanged(tracksInfo: TracksInfo) { |                 override fun onTracksInfoChanged(tracksInfo: TracksInfo) { | ||||||
|  |                     fun Format.isSubtitle(): Boolean { | ||||||
|  |                         return this.sampleMimeType?.contains("video/") == false && | ||||||
|  |                                 this.sampleMimeType?.contains("audio/") == false | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     normalSafeApiCall { | ||||||
|                         exoPlayerSelectedTracks = |                         exoPlayerSelectedTracks = | ||||||
|                         tracksInfo.trackGroupInfos.mapNotNull { it.trackGroup.getFormat(0).language?.let { lang -> lang to it.isSelected } } |                             tracksInfo.trackGroupInfos.mapNotNull { | ||||||
|  |                                 val format = it.trackGroup.getFormat(0) | ||||||
|  |                                 if (format.isSubtitle()) | ||||||
|  |                                     format.language?.let { lang -> lang to it.isSelected } | ||||||
|  |                                 else null | ||||||
|  |                             } | ||||||
|  | 
 | ||||||
|  |                         val exoPlayerReportedTracks = tracksInfo.trackGroupInfos.mapNotNull { | ||||||
|  |                             // Filter out unsupported tracks | ||||||
|  |                             if (it.isSupported) | ||||||
|  |                                 it.trackGroup.getFormat(0) | ||||||
|  |                             else | ||||||
|  |                                 null | ||||||
|  |                         }.mapNotNull { | ||||||
|  |                             // Filter out non subs, already used subs and subs without languages | ||||||
|  |                             if (!it.isSubtitle() || | ||||||
|  |                                 // Anything starting with - is not embedded | ||||||
|  |                                 it.language?.startsWith("-") == true || | ||||||
|  |                                 it.language == null | ||||||
|  |                             ) return@mapNotNull null | ||||||
|  |                             return@mapNotNull SubtitleData( | ||||||
|  |                                 // Nicer looking displayed names | ||||||
|  |                                 fromTwoLettersToLanguage(it.language!!) ?: it.language!!, | ||||||
|  |                                 // See setPreferredTextLanguage | ||||||
|  |                                 it.language!!, | ||||||
|  |                                 SubtitleOrigin.EMBEDDED_IN_VIDEO, | ||||||
|  |                                 it.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP | ||||||
|  |                             ) | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         embeddedSubtitlesFetched?.invoke(exoPlayerReportedTracks) | ||||||
|                         subtitlesUpdates?.invoke() |                         subtitlesUpdates?.invoke() | ||||||
|  |                     } | ||||||
|                     super.onTracksInfoChanged(tracksInfo) |                     super.onTracksInfoChanged(tracksInfo) | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  | @ -766,6 +819,15 @@ class CS3IPlayer : IPlayer { | ||||||
|                     // TODO |                     // TODO | ||||||
|                     throw NotImplementedError() |                     throw NotImplementedError() | ||||||
|                 } |                 } | ||||||
|  |                 SubtitleOrigin.EMBEDDED_IN_VIDEO -> { | ||||||
|  |                     if (offlineSourceFactory != null) { | ||||||
|  |                         activeSubtitles.add(sub) | ||||||
|  |                         SingleSampleMediaSource.Factory(offlineSourceFactory) | ||||||
|  |                             .createMediaSource(subConfig, C.TIME_UNSET) | ||||||
|  |                     } else { | ||||||
|  |                         null | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         return Pair(subSources, activeSubtitles) |         return Pair(subSources, activeSubtitles) | ||||||
|  |  | ||||||
|  | @ -82,6 +82,10 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|         return player.setPreferredSubtitles(sub) |         return player.setPreferredSubtitles(sub) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     override fun embeddedSubtitlesFetched(subtitles: List<SubtitleData>) { | ||||||
|  |         viewModel.addSubtitles(subtitles.toSet()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private fun noSubtitles(): Boolean { |     private fun noSubtitles(): Boolean { | ||||||
|         return setSubtitles(null) |         return setSubtitles(null) | ||||||
|     } |     } | ||||||
|  | @ -258,7 +262,7 @@ class GeneratorPlayer : FullScreenPlayer() { | ||||||
|                 var startSource = 0 |                 var startSource = 0 | ||||||
| 
 | 
 | ||||||
|                 val sortedUrls = sortLinks(useQualitySettings = false) |                 val sortedUrls = sortLinks(useQualitySettings = false) | ||||||
|                 if (sortedUrls.isNullOrEmpty()) { |                 if (sortedUrls.isEmpty()) { | ||||||
|                     sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone = true |                     sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone = true | ||||||
|                 } else { |                 } else { | ||||||
|                     startSource = sortedUrls.indexOf(currentSelectedLink) |                     startSource = sortedUrls.indexOf(currentSelectedLink) | ||||||
|  |  | ||||||
|  | @ -85,6 +85,7 @@ interface IPlayer { | ||||||
|         nextEpisode: (() -> Unit)? = null,                          // this is used by the player to load the next episode |         nextEpisode: (() -> Unit)? = null,                          // this is used by the player to load the next episode | ||||||
|         prevEpisode: (() -> Unit)? = null,                          // this is used by the player to load the previous episode |         prevEpisode: (() -> Unit)? = null,                          // this is used by the player to load the previous episode | ||||||
|         subtitlesUpdates: (() -> Unit)? = null,                     // callback from player to inform that subtitles have updated in some way |         subtitlesUpdates: (() -> Unit)? = null,                     // callback from player to inform that subtitles have updated in some way | ||||||
|  |         embeddedSubtitlesFetched: ((List<SubtitleData>) -> Unit)? = null, // callback from player to give all embedded subtitles | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     fun updateSubtitleStyle(style: SaveCaptionStyle) |     fun updateSubtitleStyle(style: SaveCaptionStyle) | ||||||
|  |  | ||||||
|  | @ -93,10 +93,18 @@ class PlayerGeneratorViewModel : ViewModel() { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * If duplicate nothing will happen | ||||||
|  |      * */ | ||||||
|     fun addSubtitles(file: Set<SubtitleData>) { |     fun addSubtitles(file: Set<SubtitleData>) { | ||||||
|         val subs = (_currentSubs.value?.toMutableSet() ?: mutableSetOf()) |         val currentSubs = _currentSubs.value ?: emptySet() | ||||||
|         subs.addAll(file) |         // Prevent duplicates | ||||||
|         _currentSubs.postValue(subs) |         val allSubs = (currentSubs + file).distinct().toSet() | ||||||
|  |         // Do not post if there's nothing new | ||||||
|  |         // Posting will refresh subtitles which will in turn | ||||||
|  |         // make the subs to english if previously unselected | ||||||
|  |         if (allSubs != currentSubs) | ||||||
|  |             _currentSubs.postValue(allSubs) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private var currentJob: Job? = null |     private var currentJob: Job? = null | ||||||
|  |  | ||||||
|  | @ -23,8 +23,13 @@ enum class SubtitleOrigin { | ||||||
|     URL, |     URL, | ||||||
|     DOWNLOADED_FILE, |     DOWNLOADED_FILE, | ||||||
|     OPEN_SUBTITLES, |     OPEN_SUBTITLES, | ||||||
|  |     EMBEDDED_IN_VIDEO | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * @param name To be displayed in the player | ||||||
|  |  * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend language | ||||||
|  |  * */ | ||||||
| data class SubtitleData( | data class SubtitleData( | ||||||
|     val name: String, |     val name: String, | ||||||
|     val url: String, |     val url: String, | ||||||
|  | @ -77,6 +82,9 @@ class PlayerSubtitleHelper { | ||||||
|                     // TODO |                     // TODO | ||||||
|                     throw NotImplementedError() |                     throw NotImplementedError() | ||||||
|                 } |                 } | ||||||
|  |                 SubtitleOrigin.EMBEDDED_IN_VIDEO -> { | ||||||
|  |                     throw NotImplementedError() | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -201,7 +201,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | ||||||
|                 putString(URL_BUNDLE, card.url) |                 putString(URL_BUNDLE, card.url) | ||||||
|                 putString(API_NAME_BUNDLE, card.apiName) |                 putString(API_NAME_BUNDLE, card.apiName) | ||||||
|                 if (card is DataStoreHelper.ResumeWatchingResult) { |                 if (card is DataStoreHelper.ResumeWatchingResult) { | ||||||
|                     println("CARD::::: $card") | //                    println("CARD::::: $card") | ||||||
|                     if (card.season != null) |                     if (card.season != null) | ||||||
|                         putInt(SEASON_BUNDLE, card.season) |                         putInt(SEASON_BUNDLE, card.season) | ||||||
|                     if (card.episode != null) |                     if (card.episode != null) | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue