/**
  * Close stream. This method can close both IClientBroadcastStream (coming from Flash Player to
  * Red5) and ISubscriberStream (from Red5 to Flash Player). Corresponding application handlers
  * (streamSubscriberClose, etc.) are called as if close was initiated by Flash Player.
  *
  * <p>It is recommended to remember stream id in application handlers, ex.:
  *
  * <pre>
  * public void streamBroadcastStart(IBroadcastStream stream) {
  * 	super.streamBroadcastStart(stream);
  * 	if (stream instanceof IClientBroadcastStream) {
  * 		int publishedStreamId = ((ClientBroadcastStream)stream).getStreamId();
  * 		Red5.getConnectionLocal().setAttribute(PUBLISHED_STREAM_ID_ATTRIBUTE, publishedStreamId);
  * 	}
  * }
  * </pre>
  *
  * <pre>
  * public void streamPlaylistItemPlay(IPlaylistSubscriberStream stream, IPlayItem item, boolean isLive) {
  * 	super.streamPlaylistItemPlay(stream, item, isLive);
  * 	Red5.getConnectionLocal().setAttribute(WATCHED_STREAM_ID_ATTRIBUTE, stream.getStreamId());
  * }
  * </pre>
  *
  * When stream is closed, corresponding NetStream status will be sent to stream provider /
  * consumers. Implementation is based on Red5's StreamService.close()
  *
  * @param conn client connection
  * @param streamId stream ID (number: 1,2,...)
  */
 public void closeStream(IConnection conn, int streamId) {
   log.info("closeStream: streamId={}, connection={}", streamId, conn);
   if (conn instanceof IStreamCapableConnection) {
     IStreamCapableConnection scConn = (IStreamCapableConnection) conn;
     IClientStream stream = scConn.getStreamById(streamId);
     if (stream != null) {
       if (stream instanceof IClientBroadcastStream) {
         // this is a broadcasting stream (from Flash Player to Red5)
         IClientBroadcastStream bs = (IClientBroadcastStream) stream;
         IBroadcastScope bsScope = getBroadcastScope(conn.getScope(), bs.getPublishedName());
         if (bsScope != null && conn instanceof BaseConnection) {
           ((BaseConnection) conn).unregisterBasicScope(bsScope);
         }
       }
       stream.close();
       scConn.deleteStreamById(streamId);
       // in case of broadcasting stream, status is sent automatically by Red5
       if (!(stream instanceof IClientBroadcastStream)) {
         StreamService.sendNetStreamStatus(
             conn,
             StatusCodes.NS_PLAY_STOP,
             "Stream closed by server",
             stream.getName(),
             Status.STATUS,
             streamId);
       }
     } else {
       log.info("Stream not found: streamId={}, connection={}", streamId, conn);
     }
   } else {
     log.warn("Connection is not instance of IStreamCapableConnection: {}", conn);
   }
 }
 /** {@inheritDoc} */
 public void seek(int position) {
   log.trace("seek - position:{}", position);
   IConnection conn = Red5.getConnectionLocal();
   if (conn instanceof IStreamCapableConnection) {
     IStreamCapableConnection streamConn = (IStreamCapableConnection) conn;
     int streamId = conn.getStreamId();
     IClientStream stream = streamConn.getStreamById(streamId);
     if (stream != null && stream instanceof ISubscriberStream) {
       ISubscriberStream subscriberStream = (ISubscriberStream) stream;
       try {
         subscriberStream.seek(position);
       } catch (OperationNotSupportedException err) {
         sendNSFailed(
             streamConn,
             StatusCodes.NS_SEEK_FAILED,
             "The stream doesn't support seeking.",
             stream.getName(),
             streamId);
       }
     }
   }
 }