/**
  * 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);
   }
 }
 /**
  * Send
  *
  * <pre>NetStream.Status</pre>
  *
  * to the client.
  *
  * @param conn
  * @param statusCode see StatusCodes class
  * @param description
  * @param name
  * @param streamId
  */
 private void sendNSStatus(
     IConnection conn, String statusCode, String description, String name, int streamId) {
   StreamService.sendNetStreamStatus(conn, statusCode, description, name, Status.STATUS, streamId);
 }
 /**
  * Send a
  *
  * <pre>NetStream.Play.Failed</pre>
  *
  * to the client.
  *
  * @param conn
  * @param errorCode
  * @param description
  * @param name
  * @param streamId
  */
 private void sendNSFailed(
     IConnection conn, String errorCode, String description, String name, int streamId) {
   StreamService.sendNetStreamStatus(conn, errorCode, description, name, Status.ERROR, streamId);
 }