int SrsRtmp::fmle_unpublish(int stream_id, double unpublish_tid)
{
	int ret = ERROR_SUCCESS;
	
	// publish response onFCUnpublish(NetStream.unpublish.Success)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
		
		pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_UNPUBLISH;
		pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess));
		pkt->data->set(StatusDescription, new SrsAmf0String("Stop publishing stream."));
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send onFCUnpublish(NetStream.unpublish.Success) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send onFCUnpublish(NetStream.unpublish.Success) message success.");
	}
	// FCUnpublish response
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(unpublish_tid);
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send FCUnpublish response message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send FCUnpublish response message success.");
	}
	// publish response onStatus(NetStream.Unpublish.Success)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
		
		pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus));
		pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeUnpublishSuccess));
		pkt->data->set(StatusDescription, new SrsAmf0String("Stream is now unpublished"));
		pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID));
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send onStatus(NetStream.Unpublish.Success) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send onStatus(NetStream.Unpublish.Success) message success.");
	}
	
	srs_info("FMLE unpublish success.");
	
	return ret;
}
int srs_rtmp_expect_message(SrsProtocol* protocol, SrsCommonMessage** pmsg, T** ppacket)
{
    *pmsg = NULL;
    *ppacket = NULL;
    
    int ret = ERROR_SUCCESS;
    
    while (true) {
        SrsCommonMessage* msg = NULL;
        if ((ret = protocol->recv_message(&msg)) != ERROR_SUCCESS) {
            srs_error("recv message failed. ret=%d", ret);
            return ret;
        }
        srs_verbose("recv message success.");
        
        if ((ret = msg->decode_packet(protocol)) != ERROR_SUCCESS) {
            delete msg;
            srs_error("decode message failed. ret=%d", ret);
            return ret;
        }
        
        T* pkt = dynamic_cast<T*>(msg->get_packet());
        if (!pkt) {
            delete msg;
            srs_trace("drop message(type=%d, size=%d, time=%"PRId64", sid=%d).", 
                msg->header.message_type, msg->header.payload_length,
                msg->header.timestamp, msg->header.stream_id);
            continue;
        }
        
        *pmsg = msg;
        *ppacket = pkt;
        break;
    }
    
    return ret;
}
int SrsRtmpClient::play(string stream, int stream_id)
{
    int ret = ERROR_SUCCESS;

    // Play(stream)
    if (true) {
        SrsCommonMessage* msg = new SrsCommonMessage();
        SrsPlayPacket* pkt = new SrsPlayPacket();
    
        pkt->stream_name = stream;
        msg->set_packet(pkt, stream_id);
        
        if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
            srs_error("send play stream failed. "
                "stream=%s, stream_id=%d, ret=%d", 
                stream.c_str(), stream_id, ret);
            return ret;
        }
    }
    
    // SetBufferLength(1000ms)
    int buffer_length_ms = 1000;
    if (true) {
        SrsCommonMessage* msg = new SrsCommonMessage();
        SrsUserControlPacket* pkt = new SrsUserControlPacket();
    
        pkt->event_type = SrcPCUCSetBufferLength;
        pkt->event_data = stream_id;
        pkt->extra_data = buffer_length_ms;
        msg->set_packet(pkt, 0);
        
        if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
            srs_error("send set buffer length failed. "
                "stream=%s, stream_id=%d, bufferLength=%d, ret=%d", 
                stream.c_str(), stream_id, buffer_length_ms, ret);
            return ret;
        }
    }
    
    // SetChunkSize
    if (true) {
        SrsCommonMessage* msg = new SrsCommonMessage();
        SrsSetChunkSizePacket* pkt = new SrsSetChunkSizePacket();
    
        pkt->chunk_size = SRS_CONF_DEFAULT_CHUNK_SIZE;
        msg->set_packet(pkt, 0);
        
        if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
            srs_error("send set chunk size failed. "
                "stream=%s, chunk_size=%d, ret=%d", 
                stream.c_str(), SRS_CONF_DEFAULT_CHUNK_SIZE, ret);
            return ret;
        }
    }
    
    return ret;
}
int SrsRtmp::start_fmle_publish(int stream_id)
{
	int ret = ERROR_SUCCESS;
	
	// FCPublish
	double fc_publish_tid = 0;
	if (true) {
		SrsCommonMessage* msg = NULL;
		SrsFMLEStartPacket* pkt = NULL;
		if ((ret = srs_rtmp_expect_message<SrsFMLEStartPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
			srs_error("recv FCPublish message failed. ret=%d", ret);
			return ret;
		}
		srs_info("recv FCPublish request message success.");
		
		SrsAutoFree(SrsCommonMessage, msg, false);
		fc_publish_tid = pkt->transaction_id;
	}
	// FCPublish response
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(fc_publish_tid);
		
		msg->set_packet(pkt, 0);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send FCPublish response message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send FCPublish response message success.");
	}
	
	// createStream
	double create_stream_tid = 0;
	if (true) {
		SrsCommonMessage* msg = NULL;
		SrsCreateStreamPacket* pkt = NULL;
		if ((ret = srs_rtmp_expect_message<SrsCreateStreamPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
			srs_error("recv createStream message failed. ret=%d", ret);
			return ret;
		}
		srs_info("recv createStream request message success.");
		
		SrsAutoFree(SrsCommonMessage, msg, false);
		create_stream_tid = pkt->transaction_id;
	}
	// createStream response
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(create_stream_tid, stream_id);
		
		msg->set_packet(pkt, 0);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send createStream response message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send createStream response message success.");
	}
	
	// publish
	if (true) {
		SrsCommonMessage* msg = NULL;
		SrsPublishPacket* pkt = NULL;
		if ((ret = srs_rtmp_expect_message<SrsPublishPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
			srs_error("recv publish message failed. ret=%d", ret);
			return ret;
		}
		srs_info("recv publish request message success.");
		
		SrsAutoFree(SrsCommonMessage, msg, false);
	}
	// publish response onFCPublish(NetStream.Publish.Start)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
		
		pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH;
		pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart));
		pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream."));
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send onFCPublish(NetStream.Publish.Start) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send onFCPublish(NetStream.Publish.Start) message success.");
	}
	// publish response onStatus(NetStream.Publish.Start)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
		
		pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus));
		pkt->data->set(StatusCode, new SrsAmf0String(StatusCodePublishStart));
		pkt->data->set(StatusDescription, new SrsAmf0String("Started publishing stream."));
		pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID));
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send onStatus(NetStream.Publish.Start) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send onStatus(NetStream.Publish.Start) message success.");
	}
	
	srs_info("FMLE publish success.");
	
	return ret;
}
int SrsRtmp::on_play_client_pause(int stream_id, bool is_pause)
{
	int ret = ERROR_SUCCESS;
	
	if (is_pause) {
		// onStatus(NetStream.Pause.Notify)
		if (true) {
			SrsCommonMessage* msg = new SrsCommonMessage();
			SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
			
			pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus));
			pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamPause));
			pkt->data->set(StatusDescription, new SrsAmf0String("Paused stream."));
			
			msg->set_packet(pkt, stream_id);
			
			if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
				srs_error("send onStatus(NetStream.Pause.Notify) message failed. ret=%d", ret);
				return ret;
			}
			srs_info("send onStatus(NetStream.Pause.Notify) message success.");
		}
		// StreamEOF
		if (true) {
			SrsCommonMessage* msg = new SrsCommonMessage();
			SrsUserControlPacket* pkt = new SrsUserControlPacket();
			
			pkt->event_type = SrcPCUCStreamEOF;
			pkt->event_data = stream_id;
			msg->set_packet(pkt, 0);
			
			if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
				srs_error("send PCUC(StreamEOF) message failed. ret=%d", ret);
				return ret;
			}
			srs_info("send PCUC(StreamEOF) message success.");
		}
	} else {
		// onStatus(NetStream.Unpause.Notify)
		if (true) {
			SrsCommonMessage* msg = new SrsCommonMessage();
			SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
			
			pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus));
			pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamUnpause));
			pkt->data->set(StatusDescription, new SrsAmf0String("Unpaused stream."));
			
			msg->set_packet(pkt, stream_id);
			
			if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
				srs_error("send onStatus(NetStream.Unpause.Notify) message failed. ret=%d", ret);
				return ret;
			}
			srs_info("send onStatus(NetStream.Unpause.Notify) message success.");
		}
		// StreanBegin
		if (true) {
			SrsCommonMessage* msg = new SrsCommonMessage();
			SrsUserControlPacket* pkt = new SrsUserControlPacket();
			
			pkt->event_type = SrcPCUCStreamBegin;
			pkt->event_data = stream_id;
			msg->set_packet(pkt, 0);
			
			if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
				srs_error("send PCUC(StreanBegin) message failed. ret=%d", ret);
				return ret;
			}
			srs_info("send PCUC(StreanBegin) message success.");
		}
	}
	
	return ret;
}
int SrsRtmp::start_play(int stream_id)
{
	int ret = ERROR_SUCCESS;
	
	// StreamBegin
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsUserControlPacket* pkt = new SrsUserControlPacket();
		
		pkt->event_type = SrcPCUCStreamBegin;
		pkt->event_data = stream_id;
		msg->set_packet(pkt, 0);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send PCUC(StreamBegin) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send PCUC(StreamBegin) message success.");
	}
	
	// onStatus(NetStream.Play.Reset)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
		
		pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus));
		pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamReset));
		pkt->data->set(StatusDescription, new SrsAmf0String("Playing and resetting stream."));
		pkt->data->set(StatusDetails, new SrsAmf0String("stream"));
		pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID));
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send onStatus(NetStream.Play.Reset) message success.");
	}
	
	// onStatus(NetStream.Play.Start)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket();
		
		pkt->data->set(StatusLevel, new SrsAmf0String(StatusLevelStatus));
		pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeStreamStart));
		pkt->data->set(StatusDescription, new SrsAmf0String("Started playing stream."));
		pkt->data->set(StatusDetails, new SrsAmf0String("stream"));
		pkt->data->set(StatusClientId, new SrsAmf0String(RTMP_SIG_CLIENT_ID));
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send onStatus(NetStream.Play.Reset) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send onStatus(NetStream.Play.Reset) message success.");
	}
	
	// |RtmpSampleAccess(false, false)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsSampleAccessPacket* pkt = new SrsSampleAccessPacket();
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send |RtmpSampleAccess(false, false) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send |RtmpSampleAccess(false, false) message success.");
	}
	
	// onStatus(NetStream.Data.Start)
	if (true) {
		SrsCommonMessage* msg = new SrsCommonMessage();
		SrsOnStatusDataPacket* pkt = new SrsOnStatusDataPacket();
		
		pkt->data->set(StatusCode, new SrsAmf0String(StatusCodeDataStart));
		
		msg->set_packet(pkt, stream_id);
		
		if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
			srs_error("send onStatus(NetStream.Data.Start) message failed. ret=%d", ret);
			return ret;
		}
		srs_info("send onStatus(NetStream.Data.Start) message success.");
	}
	
	srs_info("start play success.");
	
	return ret;
}
int SrsRtmpConn::flash_publish(SrsSource* source)
{
    int ret = ERROR_SUCCESS;
    
    if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_publish(req->vhost))) != ERROR_SUCCESS) {
        srs_error("flash check publish_refer failed. ret=%d", ret);
        return ret;
    }
    srs_verbose("flash check publish_refer success.");
    
    SrsPithyPrint pithy_print(SRS_STAGE_PUBLISH_USER);
    
    // notify the hls to prepare when publish start.
    if ((ret = source->on_publish(req)) != ERROR_SUCCESS) {
        srs_error("flash hls on_publish failed. ret=%d", ret);
        return ret;
    }
    srs_verbose("flash hls on_publish success.");
    
    while (true) {
        // switch to other st-threads.
        st_usleep(0);
        
        SrsCommonMessage* msg = NULL;
        if ((ret = rtmp->recv_message(&msg)) != ERROR_SUCCESS) {
            if (!srs_is_client_gracefully_close(ret)) {
                srs_error("flash recv identify client message failed. ret=%d", ret);
            }
            return ret;
        }

        SrsAutoFree(SrsCommonMessage, msg, false);
        
        pithy_print.set_age(msg->header.timestamp);

        // reportable
        if (pithy_print.can_print()) {
            srs_trace("<- time=%"PRId64", obytes=%"PRId64", ibytes=%"PRId64", okbps=%d, ikbps=%d", 
                pithy_print.get_age(), rtmp->get_send_bytes(), rtmp->get_recv_bytes(), rtmp->get_send_kbps(), rtmp->get_recv_kbps());
        }
    
        // process UnPublish event.
        if (msg->header.is_amf0_command() || msg->header.is_amf3_command()) {
            if ((ret = msg->decode_packet(rtmp->get_protocol())) != ERROR_SUCCESS) {
                srs_error("flash decode unpublish message failed. ret=%d", ret);
                return ret;
            }
            
            // flash unpublish.
            // TODO: maybe need to support republish.
            srs_trace("flash flash publish finished.");
            return ERROR_CONTROL_REPUBLISH;
        }

        // video, audio, data message
        if ((ret = process_publish_message(source, msg)) != ERROR_SUCCESS) {
            srs_error("flash process publish message failed. ret=%d", ret);
            return ret;
        }
    }
    
    return ret;
}
int SrsRtmpConn::fmle_publish(SrsSource* source)
{
    int ret = ERROR_SUCCESS;
    
    if ((ret = refer->check(req->pageUrl, _srs_config->get_refer_publish(req->vhost))) != ERROR_SUCCESS) {
        srs_error("fmle check publish_refer failed. ret=%d", ret);
        return ret;
    }
    srs_verbose("fmle check publish_refer success.");
    
    SrsPithyPrint pithy_print(SRS_STAGE_PUBLISH_USER);
    
    // notify the hls to prepare when publish start.
    if ((ret = source->on_publish(req)) != ERROR_SUCCESS) {
        srs_error("fmle hls on_publish failed. ret=%d", ret);
        return ret;
    }
    srs_verbose("fmle hls on_publish success.");
    
    while (true) {
        // switch to other st-threads.
        st_usleep(0);
        
        SrsCommonMessage* msg = NULL;
        if ((ret = rtmp->recv_message(&msg)) != ERROR_SUCCESS) {
            srs_error("fmle recv identify client message failed. ret=%d", ret);
            return ret;
        }

        SrsAutoFree(SrsCommonMessage, msg, false);
        
        pithy_print.set_age(msg->header.timestamp);

        // reportable
        if (pithy_print.can_print()) {
            srs_trace("<- time=%"PRId64", obytes=%"PRId64", ibytes=%"PRId64", okbps=%d, ikbps=%d", 
                pithy_print.get_age(), rtmp->get_send_bytes(), rtmp->get_recv_bytes(), rtmp->get_send_kbps(), rtmp->get_recv_kbps());
        }
    
        // process UnPublish event.
        if (msg->header.is_amf0_command() || msg->header.is_amf3_command()) {
            if ((ret = msg->decode_packet(rtmp->get_protocol())) != ERROR_SUCCESS) {
                srs_error("fmle decode unpublish message failed. ret=%d", ret);
                return ret;
            }
        
            SrsPacket* pkt = msg->get_packet();
            if (dynamic_cast<SrsFMLEStartPacket*>(pkt)) {
                SrsFMLEStartPacket* unpublish = dynamic_cast<SrsFMLEStartPacket*>(pkt);
                if ((ret = rtmp->fmle_unpublish(res->stream_id, unpublish->transaction_id)) != ERROR_SUCCESS) {
                    return ret;
                }
                return ERROR_CONTROL_REPUBLISH;
            }
            
            srs_trace("fmle ignore AMF0/AMF3 command message.");
            continue;
        }

        // video, audio, data message
        if ((ret = process_publish_message(source, msg)) != ERROR_SUCCESS) {
            srs_error("fmle process publish message failed. ret=%d", ret);
            return ret;
        }
    }
    
    return ret;
}
int SrsRtmpClient::fmle_publish(string stream, int& stream_id)
{
    stream_id = 0;
    
    int ret = ERROR_SUCCESS;
    
    // SrsFMLEStartPacket
    if (true) {
        SrsCommonMessage* msg = new SrsCommonMessage();
        SrsFMLEStartPacket* pkt = SrsFMLEStartPacket::create_release_stream(stream);
    
        msg->set_packet(pkt, 0);
        
        if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
            srs_error("send FMLE publish "
                "release stream failed. stream=%s, ret=%d", stream.c_str(), ret);
            return ret;
        }
    }
    
    // FCPublish
    if (true) {
        SrsCommonMessage* msg = new SrsCommonMessage();
        SrsFMLEStartPacket* pkt = SrsFMLEStartPacket::create_FC_publish(stream);
    
        msg->set_packet(pkt, 0);
        
        if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
            srs_error("send FMLE publish "
                "FCPublish failed. stream=%s, ret=%d", stream.c_str(), ret);
            return ret;
        }
    }
    
    // CreateStream
    if (true) {
        SrsCommonMessage* msg = new SrsCommonMessage();
        SrsCreateStreamPacket* pkt = new SrsCreateStreamPacket();
    
        pkt->transaction_id = 4;
        msg->set_packet(pkt, 0);
        
        if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
            srs_error("send FMLE publish "
                "createStream failed. stream=%s, ret=%d", stream.c_str(), ret);
            return ret;
        }
    }
    
    // expect result of CreateStream
    if (true) {
        SrsCommonMessage* msg = NULL;
        SrsCreateStreamResPacket* pkt = NULL;
        if ((ret = srs_rtmp_expect_message<SrsCreateStreamResPacket>(protocol, &msg, &pkt)) != ERROR_SUCCESS) {
            srs_error("expect create stream response message failed. ret=%d", ret);
            return ret;
        }
        SrsAutoFree(SrsCommonMessage, msg, false);
        srs_info("get create stream response message");

        stream_id = (int)pkt->stream_id;
    }

    // publish(stream)
    if (true) {
        SrsCommonMessage* msg = new SrsCommonMessage();
        SrsPublishPacket* pkt = new SrsPublishPacket();
    
        pkt->stream_name = stream;
        msg->set_packet(pkt, stream_id);
        
        if ((ret = protocol->send_message(msg)) != ERROR_SUCCESS) {
            srs_error("send FMLE publish publish failed. "
                "stream=%s, stream_id=%d, ret=%d", stream.c_str(), stream_id, ret);
            return ret;
        }
    }
    
    return ret;
}