1. MediaPlayer::MediaPlayer()
Frameworks/base/media/java/android/media/MediaPlayer.java
public MediaPlayer() {
Looper looper;
// 得到当前线程的Looper, 并创建EventHandler对象
// 注意:EventHandler类重写了handleMessage消息处理函数
if ((looper = Looper.myLooper()) !=null) {
mEventHandler = newEventHandler(this, looper);
} else if ((looper =Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this,looper);
} else {
mEventHandler = null;
}
/* Native setup requires a weakreference to our object.
* It's easier to create it here thanin C++.
*/
native_setup(new WeakReference<MediaPlayer>(this));
}
2. android_media_MediaPlayer_native_setup()
Frameworks/base/media/jni/Android_media_MediaPlayer.cpp
// Thisfunction gets some field IDs, which in turn causes class initialization.
// It iscalled from a static block in MediaPlayer, which won't run until the
// firsttime an instance of this class is used.
staticvoid
android_media_MediaPlayer_native_init(JNIEnv*env)
{
jclass clazz;
clazz =env->FindClass("android/media/MediaPlayer");
if (clazz == NULL) {
return;
}
fields.context = env->GetFieldID(clazz,"mNativeContext", "I");
if (fields.context == NULL) {
return;
}
fields.post_event =env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}
fields.surface_texture =env->GetFieldID(clazz, "mNativeSurfaceTexture", "I");
if (fields.surface_texture == NULL) {
return;
}
clazz= env->FindClass("android/net/ProxyProperties");
if (clazz == NULL) {
return;
}
fields.proxyConfigGetHost =
env->GetMethodID(clazz,"getHost", "()Ljava/lang/String;");
fields.proxyConfigGetPort =
env->GetMethodID(clazz,"getPort", "()I");
fields.proxyConfigGetExclusionList =
env->GetMethodID(clazz,"getExclusionList", "()Ljava/lang/String;");
}
staticvoid
android_media_MediaPlayer_native_setup(JNIEnv*env, jobject thiz, jobject weak_this)
{
ALOGV("native_setup");
sp<MediaPlayer> mp = new MediaPlayer();
if (mp == NULL) {
jniThrowException(env,"java/lang/RuntimeException", "Out of memory");
return;
}
// create new listener and give it toMediaPlayer
sp<JNIMediaPlayerListener> listener =new JNIMediaPlayerListener(env, thiz, weak_this);
// 将新建的MediaPlayerListener对象赋给本地 MediaPlayer对象
mp->setListener(listener);
// Stow our new C++ MediaPlayer in anopaque field in the Java object.
// 将本地MediaPlayer 对象保存在java env (context)
setMediaPlayer(env, thiz, mp);
}
staticsp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, constsp<MediaPlayer>& player)
{
Mutex::Autolock l(sLock);
sp<MediaPlayer> old =(MediaPlayer*)env->GetIntField(thiz, fields.context);
if (player.get()) {
player->incStrong((void*)setMediaPlayer);
}
if (old != 0) {
old->decStrong((void*)setMediaPlayer);
}
env->SetIntField(thiz, fields.context,(int)player.get());
return old;
}
// 本地MediaPlayer对象创建完毕
// 下面是setDataSource分析
1. MediaPlayer::setDataSource()
Frameworks/base/media/java/android/media/MediaPlayer.java
Ignored.
2. android_media_MediaPlayer_setDataSourceAndHeaders()
Frameworks/base/media/jni/Android_media_MediaPlayer.cpp
static void
android_media_MediaPlayer_setDataSourceAndHeaders(
JNIEnv *env, jobject thiz, jstring path,
jobjectArray keys, jobjectArray values) {
// 得到本地MediaPlayer对象
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
if (mp == NULL ) {
jniThrowException(env, "java/lang/IllegalStateException",NULL);
return;
}
if (path == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException",NULL);
return;
}
const char *tmp = env->GetStringUTFChars(path, NULL);
if (tmp == NULL) { // Out ofmemory
return;
}
ALOGV("setDataSource: path %s", tmp);
String8 pathStr(tmp);
env->ReleaseStringUTFChars(path, tmp);
tmp = NULL;
// We build a KeyedVector out of the key and val arrays
KeyedVector<String8, String8> headersVector;
if (!ConvertKeyValueArraysToKeyedVector(
env, keys, values, &headersVector)) {
return;
}
status_t opStatus =
mp->setDataSource(
pathStr,
headersVector.size() > 0?&headersVector : NULL);
process_media_player_call(
env, thiz, opStatus,"java/io/IOException",
"setDataSource failed.");
}
3. MediaPlayer::setDataSource()
Frameworks/av/media/libmedia/MediaPlayer.cpp
status_tMediaPlayer::setDataSource(
const char *url, constKeyedVector<String8, String8> *headers)
{
ALOGV("setDataSource(%s)", url);
status_t err = BAD_VALUE;
if (url != NULL) {
// 从服务端得到MediaPlayerService,
// 并创建本地对应的BpMediaPlayerService(BpBinder)
constsp<IMediaPlayerService>& service(getMediaPlayerService());
if (service != 0) {
// 依据BpMediaPlayerService(BpBinder)对象,
// 创建BpMediaPlayer(BpBinder) 对象
// player是一个BpMediaPlayer(BpBinder)对象
sp<IMediaPlayer>player(service->create(this, mAudioSessionId));
if ((NO_ERROR !=doSetRetransmitEndpoint(player)) ||
(NO_ERROR != player->setDataSource(url, headers))) {
player.clear();
}
// 将这个BpMediaPlay(BpBinder)对象
// 保存在本地MediaPlayer对象的mPlayer成员变量中
err = attachNewPlayer(player);
}
}
return err;
}
status_tMediaPlayer::attachNewPlayer(const sp<IMediaPlayer>& player)
{
status_t err = UNKNOWN_ERROR;
sp<IMediaPlayer> p;
{ // scope for the lock
Mutex::Autolock _l(mLock);
if ( !( (mCurrentState &MEDIA_PLAYER_IDLE) ||
(mCurrentState ==MEDIA_PLAYER_STATE_ERROR ) ) ) {
ALOGE("attachNewPlayer calledin state %d", mCurrentState);
return INVALID_OPERATION;
}
clear_l();
p = mPlayer;
// player是一个BpMediaPlayer(BpBinder)对象
// 这儿将player对象赋值给本地MediaPlayer对象的成员变量mPlayer
mPlayer =player;
if (player != 0) {
mCurrentState =MEDIA_PLAYER_INITIALIZED;
err = NO_ERROR;
} else {
ALOGE("Unable to to createmedia player");
}
}
if (p != 0) {
p->disconnect();
}
return err;
}
4. BpMediaPlayerService::create()
Frameworks/av/media/libmedia/IMediaPlayerService.cpp
classBpMediaPlayerService: public BpInterface<IMediaPlayerService>
{
virtual sp<IMediaPlayer> create(
const sp<IMediaPlayerClient>& client, int audioSessionId) {
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
data.writeStrongBinder(client->asBinder());
data.writeInt32(audioSessionId);
// remote() 返回的是BpMediaPlayerService(BpBinder)中的BpBinder
// 这儿执行的是BpBinder->transact(),
// 将通过Binder机制执行BnMediaPlayerService::onTransact()
remote()->transact(CREATE, data,&reply);
returninterface_cast<IMediaPlayer>(reply.readStrongBinder());
}
}
template<typename INTERFACE>
class BnInterface :public INTERFACE, public BBinder
{
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;
protected:
virtual IBinder* onAsBinder();
};
classIMediaPlayerService: public IInterface
{
public:
DECLARE_META_INTERFACE(MediaPlayerService);
virtual sp<IMediaRecorder> createMediaRecorder() = 0;
virtual sp<IMediaMetadataRetriever> createMetadataRetriever() = 0;
virtual sp<IMediaPlayer> create(constsp<IMediaPlayerClient>& client, int audioSessionId = 0) = 0;
virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels,audio_format_t* pFormat) = 0;
virtualsp<IMemory> decode(int fd,int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels,audio_format_t* pFormat) = 0;
…………………………………………………………………….
}
5. BnMediaPlayerService::onTransact()
Frameworks/av/media/libmedia/IMediaPlayerService.cpp
status_t BnMediaPlayerService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch (code) {
case CREATE: {
CHECK_INTERFACE(IMediaPlayerService, data, reply);
sp<IMediaPlayerClient> client =
interface_cast<IMediaPlayerClient>(data.readStrongBinder());
int audioSessionId =data.readInt32();
// create生成一个MediaPlayerService::Client对象
// 它是一个BnMediaPlayer对象
// 因此player是一个BnMediaPlayer对象
sp<IMediaPlayer> player =create(client, audioSessionId);
reply->writeStrongBinder(player->asBinder());
return NO_ERROR;
} break;
………………………………………………………………………………………………….
}
6. MediaPlayerService::create()
Frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
因为
class MediaPlayerService : publicBnMediaPlayerService
{
}
因此上述 sp<IMediaPlayer> player =create(client, audioSessionId);
就是调用
sp<IMediaPlayer>MediaPlayerService::create(const sp<IMediaPlayerClient>& client,
int audioSessionId)
{
pid_t pid = IPCThreadState::self()->getCallingPid();
int32_t connId = android_atomic_inc(&mNextConnId);
// 生成一个MediaPlayerService::Client对象
sp<Client> c = new Client(
this, pid, connId, client, audioSessionId,
IPCThreadState::self()->getCallingUid());
ALOGV("Create new client(%d) from pid %d, uid %d, ", connId,pid,
IPCThreadState::self()->getCallingUid());
wp<Client> w = c;
{
Mutex::Autolock lock(mLock);
mClients.add(w);
}
return c;
}
MediaPlayerService::Client::Client(
const sp<MediaPlayerService>& service, pid_t pid,
int32_t connId, const sp<IMediaPlayerClient>& client,
int audioSessionId, uid_t uid)
{
ALOGV("Client(%d) constructor", connId);
mPid = pid;
mConnId = connId;
mService = service;
mClient = client;
mLoop = false;
mStatus = NO_INIT;
mAudioSessionId = audioSessionId;
mUID = uid;
mRetransmitEndpointValid = false;
#if CALLBACK_ANTAGONIZER
ALOGD("create Antagonizer");
mAntagonizer = new Antagonizer(notify, this);
#endif
}
7. BpMediaPlayer::setDataSource ()
Frameworks/av/media/libmedia/IMediaPlayerService.cpp
Follow 3 中语句
if ((NO_ERROR !=doSetRetransmitEndpoint(player)) ||
(NO_ERROR != player->setDataSource(url, headers))) {
player.clear();
}
执行该调用
status_t setDataSource(const char* url,
const KeyedVector<String8, String8>* headers)
{
Parcel data, reply;
data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
data.writeCString(url);
if (headers == NULL) {
data.writeInt32(0);
} else {
// serialize the headers
data.writeInt32(headers->size());
for (size_t i = 0; i < headers->size(); ++i) {
data.writeString8(headers->keyAt(i));
data.writeString8(headers->valueAt(i));
}
}
remote()->transact(SET_DATA_SOURCE_URL, data, &reply);
return reply.readInt32();
}
8. BnMediaPlayer::onTransact ()
Frameworks/av/media/libmedia/IMediaPlayerService.cpp
status_tBnMediaPlayer::onTransact(
uint32_t code, const Parcel& data,Parcel* reply, uint32_t flags)
{
switch (code) {
case DISCONNECT: {
CHECK_INTERFACE(IMediaPlayer, data,reply);
disconnect();
return NO_ERROR;
} break;
case SET_DATA_SOURCE_URL: {
CHECK_INTERFACE(IMediaPlayer, data,reply);
const char* url =data.readCString();
KeyedVector<String8, String8>headers;
int32_t numHeaders = data.readInt32();
for (int i = 0; i < numHeaders;++i) {
String8 key =data.readString8();
String8 value =data.readString8();
headers.add(key, value);
}
reply->writeInt32(setDataSource(url,numHeaders > 0 ? &headers : NULL));
return NO_ERROR;
} break;
………………………………………………………………………………………………….
}
9. MediaPlayerService::Client::setDataSource()
Frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
status_t MediaPlayerService::Client::setDataSource(
const char *url, constKeyedVector<String8, String8> *headers)
{
ALOGV("setDataSource(%s)", url);
if (url == NULL)
return UNKNOWN_ERROR;
if ((strncmp(url, "http://", 7)== 0) ||
(strncmp(url, "https://", 8)== 0) ||
(strncmp(url, "rtsp://", 7)== 0)) {
if(!checkPermission("android.permission.INTERNET")) {
return PERMISSION_DENIED;
}
}
if (strncmp(url, "content://",10) == 0) {
// get a filedescriptor for the contentUri and
// pass it to the setDataSource(fd)method
String16 url16(url);
int fd =android::openContentProviderFile(url16);
if (fd < 0)
{
ALOGE("Couldn't open fd for%s", url);
return UNKNOWN_ERROR;
}
setDataSource(fd, 0, 0x7fffffffffLL);// this sets mStatus
close(fd);
return mStatus;
} else {
player_typeplayerType = MediaPlayerFactory::getPlayerType(this, url);
sp<MediaPlayerBase> p = setDataSource_pre(playerType);
if (p == NULL){
returnNO_INIT;
}
// 将p 赋值给mPlayer成员变量,它是一个StagefrightPlayer()对象
// 而这个StageFrightPlayer对象中的mPlayer成员变量
// 是一个AwesomePlayer对象
setDataSource_post(p, p->setDataSource(url, headers));
return mStatus;
}
}
sp<MediaPlayerBase>MediaPlayerService::Client::setDataSource_pre(
player_type playerType)
{
ALOGV("player type = %d",playerType);
// create the right type of player
sp<MediaPlayerBase> p = createPlayer(playerType);
if (p == NULL) {
return p;
}
if (!p->hardwareOutput()) {
mAudioOutput = newAudioOutput(mAudioSessionId);
static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
}
return p;
}
sp<MediaPlayerBase>MediaPlayerService::Client::createPlayer(player_type playerType)
{
// determine if we have the right playertype
sp<MediaPlayerBase> p = mPlayer;
if ((p != NULL) &&(p->playerType() != playerType)) {
ALOGV("delete player");
p.clear();
}
if (p == NULL) {
p =MediaPlayerFactory::createPlayer(playerType, this, notify);
}
if (p != NULL) {
p->setUID(mUID);
}
return p;
}
10. sp<MediaPlayerBase> MediaPlayerFactory::createPlayer ()
Frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp
sp<MediaPlayerBase>MediaPlayerFactory::createPlayer(
player_type playerType,
void* cookie,
notify_callback_f notifyFunc) {
sp<MediaPlayerBase> p;
IFactory* factory;
status_t init_result;
Mutex::Autolock lock_(&sLock);
if (sFactoryMap.indexOfKey(playerType) <0) {
ALOGE("Failed to create playerobject of type %d, no registered"
" factory",playerType);
return p;
}
factory = sFactoryMap.valueFor(playerType);
CHECK(NULL != factory);
p =factory->createPlayer();
if (p == NULL) {
ALOGE("Failed to create playerobject of type %d, create failed",
playerType);
return p;
}
init_result = p->initCheck();
if (init_result == NO_ERROR) {
p->setNotifyCallback(cookie,notifyFunc);
} else {
ALOGE("Failed to create playerobject of type %d, initCheck failed"
" (res = %d)",playerType, init_result);
p.clear();
}
return p;
}
classStagefrightPlayerFactory :
public MediaPlayerFactory::IFactory {
public:
virtual float scoreFactory(constsp<IMediaPlayer>& client,
int fd,
int64_t offset,
int64_t length,
float curScore){
char buf[20];
lseek(fd, offset, SEEK_SET);
read(fd, buf, sizeof(buf));
lseek(fd, offset, SEEK_SET);
long ident = *((long*)buf);
// Ogg vorbis?
if (ident == 0x5367674f) // 'OggS'
return 1.0;
return 0.0;
}
virtual sp<MediaPlayerBase>createPlayer() {
ALOGV(" createStagefrightPlayer");
return newStagefrightPlayer();
}
};
11. MediaPlayerService::Client::setDataSource_post ()
Frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp
Follow 9 中语句
setDataSource_post(p, p->setDataSource(url, headers));
调用该函数
voidMediaPlayerService::Client::setDataSource_post(
const sp<MediaPlayerBase>& p,
status_t status)
{
ALOGV(" setDataSource");
mStatus = status;
if (mStatus != OK) {
ALOGE(" error: %d", mStatus);
return;
}
// Set the re-transmission endpoint if onewas chosen.
if (mRetransmitEndpointValid) {
mStatus =p->setRetransmitEndpoint(&mRetransmitEndpoint);
if (mStatus != NO_ERROR) {
ALOGE("setRetransmitEndpointerror: %d", mStatus);
}
}
if (mStatus == OK){
mPlayer = p;
}
}
上面p-> setDataSource(url, headers)将调用StageFrightPlayer的setDataSource
status_tStagefrightPlayer::setDataSource(
const char *url, constKeyedVector<String8, String8> *headers) {
returnmPlayer->setDataSource(url, headers);
}
而 mPlayer-> setDataSource(url, headers); 将调用AwesomePlayer的setDataSource
status_t AwesomePlayer::setDataSource(
const char *uri, constKeyedVector<String8, String8> *headers) {
Mutex::Autolock autoLock(mLock);
returnsetDataSource_l(uri, headers);
}
status_tAwesomePlayer::setDataSource_l(
const char *uri, const KeyedVector<String8,String8> *headers) {
reset_l();
mUri = uri;
if (headers) {
mUriHeaders = *headers;
ssize_t index =mUriHeaders.indexOfKey(String8("x-hide-urls-from-log"));
if (index >= 0) {
// Browser is in"incognito" mode, suppress logging URLs.
// This isn't something that shouldbe passed to the server.
mUriHeaders.removeItemsAt(index);
modifyFlags(INCOGNITO, SET);
}
}
ALOGI("setDataSource_l(URLsuppressed)");
// The actual work will be done duringpreparation in the call to
// ::finishSetDataSource_l to avoidblocking the calling thread in
// setDataSource for any significant time.
{
Mutex::Autolock autoLock(mStatsLock);
mStats.mFd = -1;
mStats.mURI = mUri;
}
return OK;
}
12. StagefrightPlayer::StagefrightPlayer ()
Frameworks/av/media/libmediaplayerservice/StageFrightPlayer.cpp
Follow 10 中 return newStagefrightPlayer();
StagefrightPlayer::StagefrightPlayer()
: mPlayer(new AwesomePlayer) {
ALOGV("StagefrightPlayer");
mPlayer->setListener(this);
}
13. StagefrightPlayer::StagefrightPlayer ()
Frameworks/av/media/libstagefright/AwesomePlayer.cpp
AwesomePlayer::AwesomePlayer()
: mQueueStarted(false),
mUIDValid(false),
mTimeSource(NULL),
mVideoRenderingStarted(false),
mVideoRendererIsPreview(false),
mAudioPlayer(NULL),
mDisplayWidth(0),
mDisplayHeight(0),
mVideoScalingMode(NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW),
mFlags(0),
mExtractorFlags(0),
mVideoBuffer(NULL),
mDecryptHandle(NULL),
mLastVideoTimeUs(-1),
mTextDriver(NULL) {
CHECK_EQ(mClient.connect(), (status_t)OK);
DataSource::RegisterDefaultSniffers();
mVideoEvent = new AwesomeEvent(this,&AwesomePlayer::onVideoEvent);
mVideoEventPending = false;
mStreamDoneEvent = new AwesomeEvent(this,&AwesomePlayer::onStreamDone);
mStreamDoneEventPending = false;
mBufferingEvent = new AwesomeEvent(this,&AwesomePlayer::onBufferingUpdate);
mBufferingEventPending = false;
mVideoLagEvent = new AwesomeEvent(this,&AwesomePlayer::onVideoLagUpdate);
mVideoEventPending = false;
mCheckAudioStatusEvent = new AwesomeEvent(
this,&AwesomePlayer::onCheckAudioStatus);
mAudioStatusEventPending = false;
reset();
}
voidAwesomePlayer::reset() {
Mutex::Autolock autoLock(mLock);
reset_l();
}
voidAwesomePlayer::reset_l() {
mVideoRenderingStarted = false;
mActiveAudioTrackIndex = -1;
mDisplayWidth = 0;
mDisplayHeight = 0;
if (mDecryptHandle != NULL) {
mDrmManagerClient->setPlaybackStatus(mDecryptHandle,
Playback::STOP, 0);
mDecryptHandle = NULL;
mDrmManagerClient = NULL;
}
if (mFlags & PLAYING) {
uint32_t params =IMediaPlayerService::kBatteryDataTrackDecoder;
if ((mAudioSource != NULL) &&(mAudioSource != mAudioTrack)) {
params |=IMediaPlayerService::kBatteryDataTrackAudio;
}
if (mVideoSource != NULL) {
params |=IMediaPlayerService::kBatteryDataTrackVideo;
}
addBatteryData(params);
}
if (mFlags & PREPARING) {
modifyFlags(PREPARE_CANCELLED, SET);
if (mConnectingDataSource != NULL) {
ALOGI("interrupting theconnection process");
mConnectingDataSource->disconnect();
}
if (mFlags & PREPARING_CONNECTED) {
// We are basically done preparing,we're just buffering
// enough data to start playback,we can safely interrupt that.
finishAsyncPrepare_l();
}
}
while (mFlags & PREPARING) {
mPreparedCondition.wait(mLock);
}
cancelPlayerEvents();
mWVMExtractor.clear();
mCachedSource.clear();
mAudioTrack.clear();
mVideoTrack.clear();
mExtractor.clear();
// Shutdown audio first, so that therespone to the reset request
// appears to happen instantaneously as faras the user is concerned
// If we did this later, audio wouldcontinue playing while we
// shutdown the video-related resources andthe player appear to
// not be as responsive to a reset request.
if ((mAudioPlayer == NULL || !(mFlags &AUDIOPLAYER_STARTED))
&& mAudioSource != NULL) {
// If we had an audio player, it wouldhave effectively
// taken possession of the audio sourceand stopped it when
// _it_ is stopped. Otherwise this isstill our responsibility.
mAudioSource->stop();
}
mAudioSource.clear();
mTimeSource = NULL;
delete mAudioPlayer;
mAudioPlayer = NULL;
if (mTextDriver != NULL) {
delete mTextDriver;
mTextDriver = NULL;
}
mVideoRenderer.clear();
if (mVideoSource != NULL) {
shutdownVideoDecoder_l();
}
mDurationUs = -1;
modifyFlags(0, ASSIGN);
mExtractorFlags = 0;
mTimeSourceDeltaUs = 0;
mVideoTimeUs = 0;
mSeeking = NO_SEEK;
mSeekNotificationSent = true;
mSeekTimeUs = 0;
mUri.setTo("");
mUriHeaders.clear();
mFileSource.clear();
mBitrate = -1;
mLastVideoTimeUs = -1;
{
Mutex::Autolock autoLock(mStatsLock);
mStats.mFd = -1;
mStats.mURI = String8();
mStats.mBitrate = -1;
mStats.mAudioTrackIndex = -1;
mStats.mVideoTrackIndex = -1;
mStats.mNumVideoFramesDecoded = 0;
mStats.mNumVideoFramesDropped = 0;
mStats.mVideoWidth = -1;
mStats.mVideoHeight = -1;
mStats.mFlags = 0;
mStats.mTracks.clear();
}
mWatchForAudioSeekComplete = false;
mWatchForAudioEOS = false;
}