@@ -293,7 +293,7 @@ func (s *Server) handleStream(w http.ResponseWriter, r *http.Request, device *au
293293 var streams []Stream
294294 streamsList := s .streamConfigsForStreamRequest ()
295295 for _ , str := range streamsList {
296- list , err := s .buildOrderedPlayList (ctx , str .ID , contentType , id )
296+ list , err := s .buildOrderedPlayList (ctx , str .ID , contentType , id , isAIOStreams )
297297 if err != nil {
298298 logger .Error ("Error building play list" , "streamId" , str .ID , "err" , err )
299299 continue
@@ -493,7 +493,7 @@ func (s *Server) GetStreams(ctx context.Context, contentType, id string, device
493493 var streams []Stream
494494 streamsList := s .streamConfigsForStreamRequest ()
495495 for _ , str := range streamsList {
496- list , err := s .buildOrderedPlayList (ctx , str .ID , contentType , id )
496+ list , err := s .buildOrderedPlayList (ctx , str .ID , contentType , id , false )
497497 if err != nil || list == nil {
498498 continue
499499 }
@@ -725,21 +725,24 @@ type rawSearchCacheEntry struct {
725725 until time.Time
726726}
727727
728- func (s * Server ) buildOrderedPlayList (ctx context.Context , streamId , contentType , id string ) (* orderedPlayListResult , error ) {
728+ func (s * Server ) buildOrderedPlayList (ctx context.Context , streamId , contentType , id string , skipFilterSort bool ) (* orderedPlayListResult , error ) {
729729 if streamId == "" {
730730 streamId = s .getDefaultStreamID ()
731731 }
732- list , err := s .buildOrderedPlayListUncached (ctx , streamId , contentType , id )
732+ list , err := s .buildOrderedPlayListUncached (ctx , streamId , contentType , id , skipFilterSort )
733733 if err != nil || list == nil {
734734 return list , err
735735 }
736736 // Cache so play/next resolve the same list order.
737737 cacheKey := streamId + ":" + contentType + ":" + id
738+ if skipFilterSort {
739+ cacheKey += "|noFilter"
740+ }
738741 s .playListCache .Store (cacheKey , & playListCacheEntry {result : list , until : time .Now ().Add (playListCacheTTL )})
739742 return list , nil
740743}
741744
742- func (s * Server ) buildOrderedPlayListUncached (ctx context.Context , streamId , contentType , id string ) (* orderedPlayListResult , error ) {
745+ func (s * Server ) buildOrderedPlayListUncached (ctx context.Context , streamId , contentType , id string , skipFilterSort bool ) (* orderedPlayListResult , error ) {
743746 raw , err := s .getOrBuildRawSearchResult (ctx , contentType , id )
744747 if err != nil || raw == nil {
745748 return nil , err
@@ -754,7 +757,7 @@ func (s *Server) buildOrderedPlayListUncached(ctx context.Context, streamId, con
754757 if str == nil {
755758 str = s .getGlobalStream ()
756759 }
757- return s .buildOrderedPlayListFromRaw (raw , str )
760+ return s .buildOrderedPlayListFromRaw (raw , str , skipFilterSort )
758761}
759762
760763// getOrBuildRawSearchResult runs TMDB + AvailNZB + indexer search once per (contentType, id); result is shared by all streams.
@@ -1009,8 +1012,21 @@ func (s *Server) GetSearchReleases(ctx context.Context, contentType, id string)
10091012 return & SearchReleasesResponse {Streams : streamInfos , Releases : releasesOut }, nil
10101013}
10111014
1015+ // releasesToCandidates converts releases to candidates with no stream filtering (score 0, preserve order).
1016+ func releasesToCandidates (releases []* release.Release ) []triage.Candidate {
1017+ var out []triage.Candidate
1018+ for _ , rel := range releases {
1019+ if rel == nil {
1020+ continue
1021+ }
1022+ out = append (out , triage.Candidate {Release : rel , Score : 0 })
1023+ }
1024+ return out
1025+ }
1026+
10121027// buildOrderedPlayListFromRaw applies one stream's filters/sorting to raw results (triage, merge, filter, sort).
1013- func (s * Server ) buildOrderedPlayListFromRaw (raw * rawSearchResult , str * stream.Stream ) (* orderedPlayListResult , error ) {
1028+ // When skipFilterSort is true (e.g. AIOStreams), stream triage and sort are skipped; only merge, dedupe, and safety filters apply.
1029+ func (s * Server ) buildOrderedPlayListFromRaw (raw * rawSearchResult , str * stream.Stream , skipFilterSort bool ) (* orderedPlayListResult , error ) {
10141030 // Set of DetailsURLs that AvailNZB reports as unavailable — exclude these from Stremio play list.
10151031 unavailableDetailsURLs := make (map [string ]bool )
10161032 if raw .AvailResult != nil {
@@ -1024,8 +1040,14 @@ func (s *Server) buildOrderedPlayListFromRaw(raw *rawSearchResult, str *stream.S
10241040 }
10251041 }
10261042
1027- availCandidates := s .triageCandidates (str , raw .AvailReleases )
1028- indexerCandidates := s .triageCandidates (str , raw .IndexerReleases )
1043+ var availCandidates , indexerCandidates []triage.Candidate
1044+ if skipFilterSort {
1045+ availCandidates = releasesToCandidates (raw .AvailReleases )
1046+ indexerCandidates = releasesToCandidates (raw .IndexerReleases )
1047+ } else {
1048+ availCandidates = s .triageCandidates (str , raw .AvailReleases )
1049+ indexerCandidates = s .triageCandidates (str , raw .IndexerReleases )
1050+ }
10291051
10301052 seenURL := make (map [string ]bool )
10311053 var merged []triage.Candidate
@@ -1110,11 +1132,13 @@ func (s *Server) buildOrderedPlayListFromRaw(raw *rawSearchResult, str *stream.S
11101132 merged = filtered
11111133 }
11121134 }
1113- // Sort by stream's score only. AvailNZB does not override the stream's priority: we only badge/serve
1114- // [availNZB] when the stream's #1 choice happens to be AvailNZB-good.
1115- sort .Slice (merged , func (i , j int ) bool {
1116- return streamScoreFromCandidate (merged [i ]) > streamScoreFromCandidate (merged [j ])
1117- })
1135+ if ! skipFilterSort {
1136+ // Sort by stream's score only. AvailNZB does not override the stream's priority: we only badge/serve
1137+ // [availNZB] when the stream's #1 choice happens to be AvailNZB-good.
1138+ sort .Slice (merged , func (i , j int ) bool {
1139+ return streamScoreFromCandidate (merged [i ]) > streamScoreFromCandidate (merged [j ])
1140+ })
1141+ }
11181142
11191143 firstIsAvailGood := false
11201144 if len (merged ) > 0 && merged [0 ].Release != nil && merged [0 ].Release .DetailsURL != "" {
@@ -1401,11 +1425,12 @@ func (s *Server) GetAvailNZBStreams(ctx context.Context, contentType, id string,
14011425
14021426// resolveStreamSlot builds the ordered play list for the given stream, creates a deferred session for the release at index, and sets fallback URLs.
14031427// streamId empty means default stream; it is normalized to the actual id for sessionID and URLs.
1404- func (s * Server ) resolveStreamSlot (ctx context.Context , streamId , contentType , id string , index int , device * auth.Device ) (* session.Session , error ) {
1428+ // skipFilterSort when true (e.g. AIOStreams) uses raw order without stream filtering/sorting.
1429+ func (s * Server ) resolveStreamSlot (ctx context.Context , streamId , contentType , id string , index int , device * auth.Device , skipFilterSort bool ) (* session.Session , error ) {
14051430 if streamId == "" {
14061431 streamId = s .getDefaultStreamID ()
14071432 }
1408- list , err := s .buildOrderedPlayList (ctx , streamId , contentType , id )
1433+ list , err := s .buildOrderedPlayList (ctx , streamId , contentType , id , skipFilterSort )
14091434 if err != nil || list == nil {
14101435 return nil , fmt .Errorf ("build play list: %w" , err )
14111436 }
@@ -1516,7 +1541,8 @@ func (s *Server) handleNextRelease(w http.ResponseWriter, r *http.Request, devic
15161541 }
15171542 return ":" + streamId + ":" + contentType + ":" + id
15181543 })()
1519- list , err := s .buildOrderedPlayList (r .Context (), streamId , contentType , id )
1544+ isAIOStreams := strings .Contains (r .Header .Get ("User-Agent" ), "AIOStreams" )
1545+ list , err := s .buildOrderedPlayList (r .Context (), streamId , contentType , id , isAIOStreams )
15201546 maxIdx := 0
15211547 if err == nil && list != nil && len (list .Candidates ) > 1 {
15221548 maxIdx = len (list .Candidates ) - 1
@@ -1625,7 +1651,8 @@ func (s *Server) handlePlay(w http.ResponseWriter, r *http.Request, device *auth
16251651 if err != nil {
16261652 // Phase 3: resolve stream slot to a real session (create deferred for this index, set fallbacks)
16271653 if streamId , contentType , id , index , ok := parseStreamSlotID (sessionID ); ok {
1628- sess , err = s .resolveStreamSlot (r .Context (), streamId , contentType , id , index , device )
1654+ isAIOStreams := strings .Contains (r .Header .Get ("User-Agent" ), "AIOStreams" )
1655+ sess , err = s .resolveStreamSlot (r .Context (), streamId , contentType , id , index , device , isAIOStreams )
16291656 if err != nil {
16301657 logger .Debug ("Resolve stream slot failed" , "slot" , sessionID , "err" , err )
16311658 http .Error (w , "Stream slot not found or invalid" , http .StatusNotFound )
0 commit comments