@@ -3,8 +3,8 @@ import { View, TouchableOpacity, Text, PanResponder, ActivityIndicator, Animated
33
44import { Ionicons } from '@expo/vector-icons' ;
55import AdvancedVideo from '../../../AdvancedVideo' ;
6- import { CLDVideoLayerProps , ButtonPosition , ButtonLayoutDirection , SubtitleOption } from './types' ;
7- import { formatTime , handleDefaultShare , isHLSVideo , parseHLSManifest , getVideoUrl } from './utils' ;
6+ import { CLDVideoLayerProps , ButtonPosition , ButtonLayoutDirection , SubtitleOption , QualityOption } from './types' ;
7+ import { formatTime , handleDefaultShare , isHLSVideo , parseHLSManifest , parseHLSQualityLevels , getVideoUrl } from './utils' ;
88import { SubtitleCue , fetchSubtitleFile , findActiveSubtitle } from './utils/subtitleUtils' ;
99import { styles , getResponsiveStyles } from './styles' ;
1010import { TopControls , CenterControls , BottomControls , CustomButton , SubtitleDisplay } from './components' ;
@@ -27,6 +27,9 @@ interface CLDVideoLayerState {
2727 availableSubtitleTracks : SubtitleOption [ ] ;
2828 subtitleCues : SubtitleCue [ ] ;
2929 activeSubtitleText : string | null ;
30+ currentQuality : string ;
31+ isQualityMenuVisible : boolean ;
32+ availableQualityLevels : QualityOption [ ] ;
3033}
3134
3235export class CLDVideoLayer extends React . Component < CLDVideoLayerProps , CLDVideoLayerState > {
@@ -65,6 +68,9 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
6568 availableSubtitleTracks : [ ] ,
6669 subtitleCues : [ ] ,
6770 activeSubtitleText : null ,
71+ currentQuality : props . quality ?. defaultQuality || 'auto' ,
72+ isQualityMenuVisible : false ,
73+ availableQualityLevels : [ ] ,
6874 } ;
6975
7076 this . panResponder = PanResponder . create ( {
@@ -149,9 +155,10 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
149155 this . startAutoHideTimer ( ) ;
150156 }
151157
152- // Parse HLS manifest for subtitle tracks if video is HLS
158+ // Parse HLS manifest for subtitle tracks and quality levels if video is HLS
153159 setTimeout ( ( ) => {
154160 this . parseHLSSubtitlesIfNeeded ( ) ;
161+ this . parseHLSQualityLevelsIfNeeded ( ) ;
155162 } , 100 ) ;
156163
157164 // Try multiple approaches for orientation detection
@@ -168,9 +175,10 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
168175 }
169176
170177 componentDidUpdate ( prevProps : CLDVideoLayerProps ) {
171- // Re-parse subtitles if video URL changed
178+ // Re-parse subtitles and quality levels if video URL changed
172179 if ( prevProps . videoUrl !== this . props . videoUrl ) {
173180 this . parseHLSSubtitlesIfNeeded ( ) ;
181+ this . parseHLSQualityLevelsIfNeeded ( ) ;
174182 }
175183 }
176184
@@ -363,6 +371,50 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
363371 this . setState ( { isSubtitlesMenuVisible : ! this . state . isSubtitlesMenuVisible } ) ;
364372 } ;
365373
374+ handleQualityChange = async ( qualityValue : string ) => {
375+ this . setState ( { currentQuality : qualityValue } ) ;
376+
377+ if ( qualityValue === 'auto' ) {
378+ // Reset to original URL for automatic quality selection
379+ const originalUrl = getVideoUrl ( this . props . videoUrl , this . props . cldVideo ) ;
380+ if ( this . videoRef . current ) {
381+ try {
382+ await this . videoRef . current . setStatusAsync ( {
383+ uri : originalUrl ,
384+ shouldPlay : this . state . status ?. shouldPlay || false ,
385+ positionMillis : this . state . status ?. positionMillis || 0
386+ } ) ;
387+ } catch ( error ) {
388+ console . warn ( 'Failed to switch to auto quality:' , error ) ;
389+ }
390+ }
391+ return ;
392+ }
393+
394+ // Find the selected quality level
395+ const selectedQuality = this . state . availableQualityLevels . find (
396+ level => level . value === qualityValue
397+ ) ;
398+
399+ if ( selectedQuality ?. url && this . videoRef . current ) {
400+ try {
401+ await this . videoRef . current . setStatusAsync ( {
402+ uri : selectedQuality . url ,
403+ shouldPlay : this . state . status ?. shouldPlay || false ,
404+ positionMillis : this . state . status ?. positionMillis || 0
405+ } ) ;
406+ } catch ( error ) {
407+ console . warn ( 'Failed to switch to quality level:' , qualityValue , error ) ;
408+ }
409+ } else {
410+ console . warn ( 'No URL found for quality level:' , qualityValue ) ;
411+ }
412+ } ;
413+
414+ handleToggleQualityMenu = ( ) => {
415+ this . setState ( { isQualityMenuVisible : ! this . state . isQualityMenuVisible } ) ;
416+ } ;
417+
366418 /**
367419 * Parse HLS manifest to get available subtitle tracks if video is HLS
368420 */
@@ -387,6 +439,30 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
387439 }
388440 } ;
389441
442+ /**
443+ * Parse HLS manifest to get available quality levels if video is HLS
444+ */
445+ parseHLSQualityLevelsIfNeeded = async ( ) => {
446+ const videoUrl = getVideoUrl ( this . props . videoUrl , this . props . cldVideo ) ;
447+
448+ if ( isHLSVideo ( videoUrl ) ) {
449+ try {
450+ const qualityLevels = await parseHLSQualityLevels ( videoUrl ) ;
451+
452+ // Always include "Auto" option
453+ const availableQualityLevels : QualityOption [ ] = [
454+ { value : 'auto' , label : 'Auto' } ,
455+ ...qualityLevels
456+ ] ;
457+
458+ this . setState ( { availableQualityLevels } ) ;
459+ } catch ( error ) {
460+ console . warn ( 'Failed to parse HLS quality levels:' , error ) ;
461+ this . setState ( { availableQualityLevels : [ { value : 'auto' , label : 'Auto' } ] } ) ;
462+ }
463+ }
464+ } ;
465+
390466 /**
391467 * Update active subtitle text based on current video time
392468 */
@@ -470,10 +546,11 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
470546 fullScreen,
471547 playbackSpeed,
472548 subtitles,
549+ quality,
473550 buttonGroups = [ ] ,
474551 titleLeftOffset
475552 } = this . props ;
476- const { status, isLandscape, isFullScreen, availableSubtitleTracks } = this . state ;
553+ const { status, isLandscape, isFullScreen, availableSubtitleTracks, availableQualityLevels } = this . state ;
477554 const progress = this . getProgress ( ) ;
478555 const currentPosition = this . getCurrentPosition ( ) ;
479556 const isVideoLoaded = status ?. isLoaded === true ;
@@ -489,6 +566,14 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
489566 defaultLanguage : subtitles ?. defaultLanguage || 'off'
490567 } : subtitles ;
491568
569+ // Create dynamic quality config based on HLS availability
570+ const dynamicQuality = isHLSVideo ( effectiveVideoUrl ) && quality ?. enabled ? {
571+ ...quality ,
572+ enabled : true ,
573+ qualities : availableQualityLevels . length > 0 ? availableQualityLevels : [ { value : 'auto' , label : 'Auto' } ] ,
574+ defaultQuality : quality ?. defaultQuality || 'auto'
575+ } : quality ;
576+
492577 // Get responsive styles based on current orientation
493578 const responsiveStyles = getResponsiveStyles ( isLandscape ) ;
494579
@@ -579,6 +664,11 @@ export class CLDVideoLayer extends React.Component<CLDVideoLayerProps, CLDVideoL
579664 onSubtitleChange = { this . handleSubtitleChange }
580665 isSubtitlesMenuVisible = { this . state . isSubtitlesMenuVisible }
581666 onToggleSubtitlesMenu = { this . handleToggleSubtitlesMenu }
667+ quality = { dynamicQuality }
668+ currentQuality = { this . state . currentQuality }
669+ onQualityChange = { this . handleQualityChange }
670+ isQualityMenuVisible = { this . state . isQualityMenuVisible }
671+ onToggleQualityMenu = { this . handleToggleQualityMenu }
582672 buttonGroups = { buttonGroups }
583673 />
584674 </ View >
0 commit comments