@ -128,6 +128,7 @@ public class StreamController { 
			
		
	
		
		
			
				
					
					            MediaFile  file  =  getSingleFile ( request ) ;              MediaFile  file  =  getSingleFile ( request ) ;   
			
		
	
		
		
			
				
					
					            boolean  isSingleFile  =  file  ! =  null ;              boolean  isSingleFile  =  file  ! =  null ;   
			
		
	
		
		
			
				
					
					            HttpRange  range  =  null ;              HttpRange  range  =  null ;   
			
		
	
		
		
			
				
					
					            Long  fileLengthExpected  =  null ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					            if  ( isSingleFile )  {              if  ( isSingleFile )  {   
			
		
	
		
		
			
				
					
					
 
			
		
	
	
		
		
			
				
					
						
						
						
							
								 
						
					 
					@ -153,39 +154,36 @@ public class StreamController { 
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                TranscodingService . Parameters  parameters  =  transcodingService . getParameters ( file ,  player ,  maxBitRate ,                  TranscodingService . Parameters  parameters  =  transcodingService . getParameters ( file ,  player ,  maxBitRate ,   
			
		
	
		
		
			
				
					
					                        preferredTargetFormat ,  null ) ;                          preferredTargetFormat ,  null ) ;   
			
		
	
		
		
			
				
					
					                boolean  isConversion  =  parameters . isDownsample ( )  | |  parameters . isTranscode ( ) ;   
			
		
	
		
		
			
				
					
					                boolean  estimateContentLength  =  ServletRequestUtils . getBooleanParameter ( request ,   
			
		
	
		
		
			
				
					
					                        "estimateContentLength" ,  false ) ;   
			
		
	
		
		
			
				
					
					                boolean  isHls  =  ServletRequestUtils . getBooleanParameter ( request ,  "hls" ,  false ) ;                  boolean  isHls  =  ServletRequestUtils . getBooleanParameter ( request ,  "hls" ,  false ) ;   
			
		
	
		
		
			
				
					
					                fileLengthExpected  =  parameters . getExpectedLength ( ) ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                // Wrangle response length and ranges.
                  // Wrangle response length and ranges.
   
			
		
	
		
		
			
				
					
					                //
                  //
   
			
		
	
		
		
			
				
					
					                // Support ranges as long as we're not transcoding; video is always assumed to transcode
                  // Support ranges as long as we're not transcoding; video is always assumed to transcode
   
			
		
	
		
		
			
				
					
					                if  ( isConversion  | |  file . isVideo ( ) )  {                  if  ( file . isVideo ( ) )  {   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					                    // Use chunked transfer; do not accept range requests
                      // Use chunked transfer; do not accept range requests
   
			
		
	
		
		
			
				
					
					                    response . setStatus ( HttpServletResponse . SC_OK ) ;                      response . setStatus ( HttpServletResponse . SC_OK ) ;   
			
		
	
		
		
			
				
					
					                    response . setHeader ( "Accept-Ranges" ,  "none" ) ;                      response . setHeader ( "Accept-Ranges" ,  "none" ) ;   
			
		
	
		
		
			
				
					
					                }  else  {                  }  else  {   
			
		
	
		
		
			
				
					
					                    // Not transcoding, partial content permitted because we know the final size
                      // Not transcoding, partial content permitted because we know the final size
   
			
		
	
		
		
			
				
					
					                    long  contentLength ;                      long  contentLength ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                    // If range was requested, respond in kind
                      // If range was requested, respond in kind
   
			
		
	
		
		
			
				
					
					                    range  =  getRange ( request ,  file ) ;                      range  =  getRange ( request ,  file . getDurationSeconds ( ) ,  fileLengthExpected ) ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					                    if  ( range  ! =  null )  {                      if  ( range  ! =  null )  {   
			
		
	
		
		
			
				
					
					                        response . setStatus ( HttpServletResponse . SC_PARTIAL_CONTENT ) ;                          response . setStatus ( HttpServletResponse . SC_PARTIAL_CONTENT ) ;   
			
		
	
		
		
			
				
					
					                        response . setHeader ( "Accept-Ranges" ,  "bytes" ) ;                          response . setHeader ( "Accept-Ranges" ,  "bytes" ) ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                        // Both ends are inclusive
                          // Both ends are inclusive
   
			
		
	
		
		
			
				
					
					                        long  startByte  =  range . getFirstBytePos ( ) ;                          long  startByte  =  range . getFirstBytePos ( ) ;   
			
		
	
		
		
			
				
					
					                        long  endByte  =  range . isClosed ( )  ?  range . getLastBytePos ( )  :  file . getFileSize ( ) -  1 ;                          long  endByte  =  range . isClosed ( )  ?  range . getLastBytePos ( )  :  fileLengthExpected   -  1 ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                        response . setHeader ( "Content-Range" ,                          response . setHeader ( "Content-Range" ,   
			
		
	
		
		
			
				
					
					                                String . format ( "bytes %d-%d/%d" ,  startByte ,  endByte ,  file . getFileSize ( ) ) ) ;                                  String . format ( "bytes %d-%d/%d" ,  startByte ,  endByte ,  fileLengthExpected  ) ) ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					                        contentLength  =  endByte  +  1  -  startByte ;                          contentLength  =  endByte  +  1  -  startByte ;   
			
		
	
		
		
			
				
					
					                    }  else  {                      }  else  {   
			
		
	
		
		
			
				
					
					                        // No range was requested, give back the whole file
                          // No range was requested, give back the whole file
   
			
		
	
		
		
			
				
					
					                        response . setStatus ( HttpServletResponse . SC_OK ) ;                          response . setStatus ( HttpServletResponse . SC_OK ) ;   
			
		
	
		
		
			
				
					
					                        contentLength  =  file . getFileSize ( ) ;                          contentLength  =  fileLengthExpected  ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					                    }                      }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                    response . setIntHeader ( "ETag" ,  file . getId ( ) ) ;                      response . setIntHeader ( "ETag" ,  file . getId ( ) ) ;   
			
		
	
	
		
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
					@ -212,6 +210,10 @@ public class StreamController { 
			
		
	
		
		
			
				
					
					                return ;                  return ;   
			
		
	
		
		
			
				
					
					            }              }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					            if  ( fileLengthExpected  ! =  null )  {   
			
		
	
		
		
			
				
					
					                LOG . info ( "Streaming request for [{}] with range [{}]" ,  file . getPath ( ) ,  response . getHeader ( "Content-Range" ) ) ;   
			
		
	
		
		
			
				
					
					            }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					            // Terminate any other streams to this player.
              // Terminate any other streams to this player.
   
			
		
	
		
		
			
				
					
					            if  ( ! isPodcast  & &  ! isSingleFile )  {              if  ( ! isPodcast  & &  ! isSingleFile )  {   
			
		
	
		
		
			
				
					
					                for  ( TransferStatus  streamStatus  :  statusService . getStreamStatusesForPlayer ( player ) )  {                  for  ( TransferStatus  streamStatus  :  statusService . getStreamStatusesForPlayer ( player ) )  {   
			
		
	
	
		
		
			
				
					
						
						
						
							
								 
						
					 
					@ -230,25 +232,31 @@ public class StreamController { 
			
		
	
		
		
			
				
					
					            )  {              )  {   
			
		
	
		
		
			
				
					
					                final  int  BUFFER_SIZE  =  2048 ;                  final  int  BUFFER_SIZE  =  2048 ;   
			
		
	
		
		
			
				
					
					                byte [ ]  buf  =  new  byte [ BUFFER_SIZE ] ;                  byte [ ]  buf  =  new  byte [ BUFFER_SIZE ] ;   
			
		
	
		
		
			
				
					
					                long  bytesWritten  =  0 ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                while  ( ! status . terminated ( ) )  {                  while  ( ! status . terminated ( ) )  {   
			
		
	
		
		
			
				
					
					                    if  ( player . getPlayQueue ( ) . getStatus ( )  = =  PlayQueue . Status . STOPPED )  {                      if  ( player . getPlayQueue ( ) . getStatus ( )  = =  PlayQueue . Status . STOPPED )  {   
			
		
	
		
		
			
				
					
					                        if  ( isPodcast  | |  isSingleFile )  {                          if  ( isPodcast  | |  isSingleFile )  {   
			
		
	
		
		
			
				
					
					                            break ;                              break ;   
			
		
	
		
		
			
				
					
					                        }  else  {                          }  else  {   
			
		
	
		
		
			
				
					
					                            sendDummy ( buf ,  out ) ;                              sendDummyDelayed  ( buf ,  out ) ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					                        }                          }   
			
		
	
		
		
			
				
					
					                    }  else  {                      }  else  {   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					                        int  n  =  in . read ( buf ) ;                          int  n  =  in . read ( buf ) ;   
			
		
	
		
		
			
				
					
					                        if  ( n  = =  - 1 )  {                          if  ( n  = =  - 1 )  {   
			
		
	
		
		
			
				
					
					                            if  ( isPodcast  | |  isSingleFile )  {                              if  ( isPodcast  | |  isSingleFile )  {   
			
		
	
		
		
			
				
					
					                                // Pad the output if needed to avoid content length errors on transcodes
   
			
		
	
		
		
			
				
					
					                                if  ( fileLengthExpected  ! =  null  & &  bytesWritten  <  fileLengthExpected )  {   
			
		
	
		
		
			
				
					
					                                    sendDummy ( buf ,  out ,  fileLengthExpected  -  bytesWritten ) ;   
			
		
	
		
		
			
				
					
					                                }   
			
		
	
		
		
			
				
					
					                                break ;                                  break ;   
			
		
	
		
		
			
				
					
					                            }  else  {                              }  else  {   
			
		
	
		
		
			
				
					
					                                sendDummy ( buf ,  out ) ;                                  sendDummyDelayed  ( buf ,  out ) ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					                            }                              }   
			
		
	
		
		
			
				
					
					                        }  else  {                          }  else  {   
			
		
	
		
		
			
				
					
					                            out . write ( buf ,  0 ,  n ) ;                              out . write ( buf ,  0 ,  n ) ;   
			
		
	
		
		
			
				
					
					                            bytesWritten  + =  n ;   
			
		
	
		
		
			
				
					
					                        }                          }   
			
		
	
		
		
			
				
					
					                    }                      }   
			
		
	
		
		
			
				
					
					                }                  }   
			
		
	
	
		
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
					@ -340,11 +348,12 @@ public class StreamController { 
			
		
	
		
		
			
				
					
					            return  file . getFileSize ( ) ;              return  file . getFileSize ( ) ;   
			
		
	
		
		
			
				
					
					        }          }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					        return  duration  *  ( long ) maxBitRate  *  1000L  /  8L ;          // Over-estimate size a bit (2 seconds) so don't cut off early in case of small calculation differences
   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					        return  ( duration  +  2 )  *  ( long ) maxBitRate  *  1000L  /  8L ;   
			
		
	
		
		
			
				
					
					    }      }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					    @Nullable      @Nullable   
			
		
	
		
		
			
				
					
					    private  HttpRange  getRange ( HttpServletRequest  request ,  MediaFile  fil e)  {      private  HttpRange  getRange ( HttpServletRequest  request ,  Integer  fileDuration ,  Long  fileSiz e)  {   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					        // First, look for "Range" HTTP header.
          // First, look for "Range" HTTP header.
   
			
		
	
		
		
			
				
					
					        HttpRange  range  =  HttpRange . valueOf ( request . getHeader ( "Range" ) ) ;          HttpRange  range  =  HttpRange . valueOf ( request . getHeader ( "Range" ) ) ;   
			
		
	
	
		
		
			
				
					
						
						
						
							
								 
						
					 
					@ -354,27 +363,25 @@ public class StreamController { 
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					        // Second, look for "offsetSeconds" request parameter.
          // Second, look for "offsetSeconds" request parameter.
   
			
		
	
		
		
			
				
					
					        String  offsetSeconds  =  request . getParameter ( "offsetSeconds" ) ;          String  offsetSeconds  =  request . getParameter ( "offsetSeconds" ) ;   
			
		
	
		
		
			
				
					
					        range  =  parseAndConvertOffsetSeconds ( offsetSeconds ,  file ) ;          range  =  parseAndConvertOffsetSeconds ( offsetSeconds ,  fileDuration ,  fileSize  ) ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					        return  range ;          return  range ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					    }      }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					    @Nullable      @Nullable   
			
		
	
		
		
			
				
					
					    private  HttpRange  parseAndConvertOffsetSeconds ( String  offsetSeconds ,  MediaFile  fil e)  {      private  HttpRange  parseAndConvertOffsetSeconds ( String  offsetSeconds ,  Integer  fileDuration ,  Long  fileSiz e)  {   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					        if  ( offsetSeconds  = =  null )  {          if  ( offsetSeconds  = =  null )  {   
			
		
	
		
		
			
				
					
					            return  null ;              return  null ;   
			
		
	
		
		
			
				
					
					        }          }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					        try  {          try  {   
			
		
	
		
		
			
				
					
					            Integer  duration  =  file . getDurationSeconds ( ) ;              if  ( fileDuration  = =  null  | |  fileSize  = =  null )  {   
			
				
				
			
		
	
		
		
			
				
					
					            Long  fileSize  =  file . getFileSize ( ) ;   
			
		
	
		
		
			
				
					
					            if  ( duration  = =  null  | |  fileSize  = =  null )  {   
			
		
	
		
		
	
		
		
			
				
					
					                return  null ;                  return  null ;   
			
		
	
		
		
			
				
					
					            }              }   
			
		
	
		
		
			
				
					
					            float  offset  =  Float . parseFloat ( offsetSeconds ) ;              float  offset  =  Float . parseFloat ( offsetSeconds ) ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					            // Convert from time offset to byte offset.
              // Convert from time offset to byte offset.
   
			
		
	
		
		
			
				
					
					            long  byteOffset  =  ( long )  ( fileSize  *  ( offset  /  d uration) ) ;              long  byteOffset  =  ( long )  ( fileSize  *  ( offset  /  fileD uration) ) ;   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					            return  new  HttpRange ( byteOffset ,  null ) ;              return  new  HttpRange ( byteOffset ,  null ) ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					        }  catch  ( Exception  x )  {          }  catch  ( Exception  x )  {   
			
		
	
	
		
		
			
				
					
						
							
								 
						
						
							
								 
						
						
					 
					@ -455,17 +462,28 @@ public class StreamController { 
			
		
	
		
		
			
				
					
					        return  size  +  ( size  %  2 ) ;          return  size  +  ( size  %  2 ) ;   
			
		
	
		
		
			
				
					
					    }      }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					    private  void  sendDummy ( byte [ ]  buf ,  OutputStream  out ,  long  len )  throws  IOException  {   
			
		
	
		
		
			
				
					
					        long  bytesWritten  =  0 ;   
			
		
	
		
		
			
				
					
					        int  n ;   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					        Arrays . fill ( buf ,  ( byte )  0xFF ) ;   
			
		
	
		
		
			
				
					
					        while  ( bytesWritten  <  len )  {   
			
		
	
		
		
			
				
					
					            n  =  ( int )  Math . min ( buf . length ,  len  -  bytesWritten ) ;   
			
		
	
		
		
			
				
					
					            out . write ( buf ,  0 ,  n ) ;   
			
		
	
		
		
			
				
					
					            bytesWritten  + =  n ;   
			
		
	
		
		
			
				
					
					        }   
			
		
	
		
		
			
				
					
					    }   
			
		
	
		
		
			
				
					
					
 
			
		
	
		
		
			
				
					
					    / * *      / * *   
			
		
	
		
		
			
				
					
					     *  Feed  the  other  end  with  some  dummy  data  to  keep  it  from  reconnecting .       *  Feed  the  other  end  with  some  dummy  data  to  keep  it  from  reconnecting .   
			
		
	
		
		
			
				
					
					     * /       * /   
			
		
	
		
		
			
				
					
					    private  void  sendDummy ( byte [ ]  buf ,  OutputStream  out )  throws  IOException  {      private  void  sendDummyDelayed  ( byte [ ]  buf ,  OutputStream  out )  throws  IOException  {   
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					        try  {          try  {   
			
		
	
		
		
			
				
					
					            Thread . sleep ( 2000 ) ;              Thread . sleep ( 2000 ) ;   
			
		
	
		
		
			
				
					
					        }  catch  ( InterruptedException  x )  {          }  catch  ( InterruptedException  x )  {   
			
		
	
		
		
			
				
					
					            LOG . warn ( "Interrupted in sleep." ,  x ) ;              LOG . warn ( "Interrupted in sleep." ,  x ) ;   
			
		
	
		
		
			
				
					
					        }          }   
			
		
	
		
		
			
				
					
					        Arrays . fill ( buf ,  ( byte )  0xFF ) ;          sendDummy ( buf ,  out ,  buf . length ) ;   
			
				
				
			
		
	
		
		
			
				
					
					        out . write ( buf ) ;   
			
		
	
		
		
	
		
		
			
				
					
					        out . flush ( ) ;          out . flush ( ) ;   
			
		
	
		
		
			
				
					
					    }      }   
			
		
	
		
		
			
				
					
					} }