diff --git a/android/contrib/tools/do-compile-ffmpeg.sh b/android/contrib/tools/do-compile-ffmpeg.sh index 5bf8d17bd6..1822ac7978 100755 --- a/android/contrib/tools/do-compile-ffmpeg.sh +++ b/android/contrib/tools/do-compile-ffmpeg.sh @@ -287,6 +287,7 @@ if [ -f "${FF_DEP_OPENSSL_LIB}/libssl.a" ]; then echo "OpenSSL detected" # FF_CFG_FLAGS="$FF_CFG_FLAGS --enable-nonfree" FF_CFG_FLAGS="$FF_CFG_FLAGS --enable-openssl" + FF_CFG_FLAGS="$FF_CFG_FLAGS --enable-protocol=crypto" echo "${FF_DEP_OPENSSL_INC} ${FF_DEP_OPENSSL_LIB}" FF_CFLAGS="$FF_CFLAGS -I${FF_DEP_OPENSSL_INC}" FF_DEP_LIBS="$FF_DEP_LIBS -L${FF_DEP_OPENSSL_LIB} -lssl -lcrypto" diff --git a/android/ijkplayer/ijkplayer-example/src/main/java/tv/danmaku/ijk/media/example/widget/media/IjkVideoView.java b/android/ijkplayer/ijkplayer-example/src/main/java/tv/danmaku/ijk/media/example/widget/media/IjkVideoView.java index fe1a49a9f7..f253783fad 100755 --- a/android/ijkplayer/ijkplayer-example/src/main/java/tv/danmaku/ijk/media/example/widget/media/IjkVideoView.java +++ b/android/ijkplayer/ijkplayer-example/src/main/java/tv/danmaku/ijk/media/example/widget/media/IjkVideoView.java @@ -149,7 +149,6 @@ public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr, int d super(context, attrs, defStyleAttr, defStyleRes); initVideoView(context); } - // REMOVED: onMeasure // REMOVED: onInitializeAccessibilityEvent // REMOVED: onInitializeAccessibilityNodeInfo diff --git a/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java b/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java index 290a696f43..3de02b4ec1 100755 --- a/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java +++ b/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java @@ -634,6 +634,8 @@ public void setScreenOnWhilePlaying(boolean screenOn) { } } + + @SuppressLint("Wakelock") private void stayAwake(boolean awake) { if (mWakeLock != null) { @@ -971,6 +973,15 @@ public MediaInfo getMediaInfo() { } return mediaInfo; } + public int startRecord(String recordVideoPath){ + return _startRecord(recordVideoPath); + } + public int stopRecord(){ + return _stopRecord(); + } + private native int _startRecord(String recordVideoPath); + + private native int _stopRecord(); @Override public void setLogEnabled(boolean enable) { diff --git a/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java b/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java index 918cd0a16d..d48f79e139 100644 --- a/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java +++ b/android/ijkplayer/ijkplayer-java/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java @@ -357,4 +357,5 @@ public void setLooping(boolean looping) { public boolean isLooping() { return mBackEndMediaPlayer.isLooping(); } + } diff --git a/ijkmedia/ijkplayer/android/ijkplayer_jni.c b/ijkmedia/ijkplayer/android/ijkplayer_jni.c index a980a641ad..3cb52846b9 100755 --- a/ijkmedia/ijkplayer/android/ijkplayer_jni.c +++ b/ijkmedia/ijkplayer/android/ijkplayer_jni.c @@ -419,6 +419,41 @@ IjkMediaPlayer_reset(JNIEnv *env, jobject thiz) ijkmp_dec_ref_p(&mp); } +//开始录屏 +static jint +IjkMediaPlayer_startRecord(JNIEnv *env, jobject thiz,jstring file) +{ + jint retval = 0; + IjkMediaPlayer *mp = jni_get_media_player(env, thiz); + printf("########################"); + printf("%p",mp); + JNI_CHECK_GOTO(mp, env, NULL, "mpjni: startRecord: null mp", LABEL_RETURN); + const char *nativeString = (*env)->GetStringUTFChars(env, file, 0); + retval = ijkmp_start_record(mp,nativeString); + + LABEL_RETURN: + printf("########################"); + printf("LABEL_RETURN"); + ijkmp_dec_ref_p(&mp); + return retval; +} + +//结束录屏 +static jint +IjkMediaPlayer_stopRecord(JNIEnv *env, jobject thiz) +{ + jint retval = 0; + IjkMediaPlayer *mp = jni_get_media_player(env, thiz); + JNI_CHECK_GOTO(mp, env, NULL, "mpjni: stopRecord: null mp", LABEL_RETURN); + + retval = ijkmp_stop_record(mp); + + LABEL_RETURN: + ijkmp_dec_ref_p(&mp); + return retval; +} + + static void IjkMediaPlayer_setLoopCount(JNIEnv *env, jobject thiz, jint loop_count) { @@ -1249,6 +1284,8 @@ static JNINativeMethod g_methods[] = { { "native_setLogLevel", "(I)V", (void *) IjkMediaPlayer_native_setLogLevel }, { "_setFrameAtTime", "(Ljava/lang/String;JJII)V", (void *) IjkMediaPlayer_setFrameAtTime }, + { "_startRecord", "(Ljava/lang/String;)I", (void *) IjkMediaPlayer_startRecord }, + { "_stopRecord", "()I", (void *) IjkMediaPlayer_stopRecord }, }; JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) diff --git a/ijkmedia/ijkplayer/ff_ffplay.c b/ijkmedia/ijkplayer/ff_ffplay.c index 8bde6c1bfd..3c4a5f7075 100755 --- a/ijkmedia/ijkplayer/ff_ffplay.c +++ b/ijkmedia/ijkplayer/ff_ffplay.c @@ -3679,6 +3679,13 @@ static int read_thread(void *arg) } } } + //录制流程 + if (ffp->is_record) { // 可以录制时,写入文件 + if (0 != ffp_record_file(ffp, pkt)) { + ffp->record_error = 1; + ffp_stop_record(ffp); + } + } } ret = 0; @@ -5168,6 +5175,245 @@ void ffp_set_property_int64(FFPlayer *ffp, int id, int64_t value) break; } } + +//开始录制函数:file_name是保存路径 +int ffp_start_record(FFPlayer *ffp, const char *file_name) +{ + assert(ffp); + + VideoState *is = ffp->is; + avcodec_register_all(); + ffp->m_ofmt_ctx = NULL; + ffp->m_ofmt = NULL; + ffp->is_record = 0; + ffp->record_error = 0; + + if (!file_name || !strlen(file_name)) { // 没有路径 + av_log(ffp, AV_LOG_ERROR, "filename is invalid\n"); + goto end; + } + + if (!is || !is->ic|| is->paused || is->abort_request) { // 没有上下文,或者上下文已经停止 + av_log(ffp, AV_LOG_ERROR, "is,is->ic,is->paused is invalid\n"); + goto end; + } + + if (ffp->is_record) { // 已经在录制 + av_log(ffp, AV_LOG_ERROR, "recording has started\n"); + goto end; + } + + AVOutputFormat *oformat = av_guess_format(NULL, file_name, NULL); + // 初始化一个用于输出的AVFormatContext结构体 + avformat_alloc_output_context2(&ffp->m_ofmt_ctx, oformat, NULL, file_name); + av_log(NULL, AV_LOG_INFO, "===== 初始化一个用于输出的AVFormatContext结构体 =====\n"); + if (!ffp->m_ofmt_ctx) { + av_log(ffp, AV_LOG_ERROR, "Could not create output context filename is %s\n", file_name); + goto end; + } + ffp->m_ofmt = ffp->m_ofmt_ctx->oformat; + + for (int i = 0; i < is->ic->nb_streams; i++) { + // 对照输入流创建输出流通道 + AVStream *out_stream; + AVStream *in_stream = is->ic->streams[i]; + AVCodecParameters *in_codecpar = in_stream->codecpar; + if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO && + in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO && + in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { + continue; + } + + out_stream = avformat_new_stream(ffp->m_ofmt_ctx, NULL); + + if (!out_stream) { + av_log(ffp, AV_LOG_ERROR, "Failed allocating output stream\n"); + goto end; + } + + // 将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体 + av_log(NULL, AV_LOG_INFO, "===== 将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体 =====\n"); + if (avcodec_parameters_copy(out_stream->codecpar, in_codecpar) < 0) { + av_log(ffp, AV_LOG_ERROR, "Failed to copy codec parameters\n"); + av_log(NULL, AV_LOG_INFO, "===== 将输入视频/音频的参数拷贝至输出视频/音频的AVCodecContext结构体,拷贝失败 =====\n"); + goto end; + } + out_stream->codecpar->codec_tag = 0; + //视频流中经常缺少重要参数,会导致写文件头失败,特此在这里处理 + if(out_stream->codecpar->width<=0){ + out_stream->codecpar->width=1920; + } + if(out_stream->codecpar->height<=0){ + out_stream->codecpar->height=1080; + } + if (!out_stream->codecpar->block_align) + out_stream->codecpar->block_align = out_stream->codecpar->channels * + av_get_bits_per_sample(out_stream->codecpar->codec_id) >> 3; + if(out_stream->codecpar->sample_rate<=0) + out_stream->codecpar->sample_rate = 44100; + int ret = avcodec_copy_context(out_stream->codec, in_stream->codec); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Failed to copy context from input to output stream codec context, error:%d", ret); + goto end; + } + if (ffp->m_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) + out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; +// out_stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + } + + av_dump_format(ffp->m_ofmt_ctx, 0, file_name, 1); + + // 打开输出文件 + if (!(ffp->m_ofmt->flags & AVFMT_NOFILE)) { + if (avio_open(&ffp->m_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0) { + av_log(ffp, AV_LOG_ERROR, "Could not open output file '%s'", file_name); + goto end; + } + } + + // 写视频文件头 + av_log(NULL, AV_LOG_INFO, "===== 写视频文件头 =====\n"); + int state = avformat_write_header(ffp->m_ofmt_ctx, NULL); + av_log(NULL, AV_LOG_INFO, "===== 写视频文件头 =====返回值:%d\n",state); + if (state < 0) { + av_log(NULL, AV_LOG_INFO, "===== 写视频文件头失败 =====\n"); + av_log(ffp, AV_LOG_ERROR, "Error occurred when opening output file\n"); + goto end; + } + ffp->is_first = 0; + ffp->is_record = 1; + ffp->record_error = 0; + av_log(NULL, AV_LOG_INFO, "===== 文件录制开启成功 =====\n"); + pthread_mutex_init(&ffp->record_mutex, NULL); + + return 0; +end: + ffp->record_error = 1; + return -1; +} + +//停止录播 +int ffp_stop_record(FFPlayer *ffp) +{ + pthread_mutex_lock(&ffp->record_mutex); + assert(ffp); + av_log(NULL, AV_LOG_INFO, "===== 文件录制结束流程 =====\n"); + if (ffp->is_record) { + ffp->is_record = 0; + + if (ffp->m_ofmt_ctx != NULL) { + av_log(NULL, AV_LOG_INFO, "===== 开始写文件尾部 =====\n"); + int errorCode = av_write_trailer(ffp->m_ofmt_ctx); + av_log(NULL, AV_LOG_INFO, "===== 写文件尾部返回:%d =====\n",errorCode); + if (ffp->m_ofmt_ctx && !(ffp->m_ofmt->flags & AVFMT_NOFILE)) { + av_log(NULL, AV_LOG_INFO, "===== 开始关闭文件 =====\n"); + avio_closep(&ffp->m_ofmt_ctx->pb); + avio_close(ffp->m_ofmt_ctx->pb); + } + avformat_free_context(ffp->m_ofmt_ctx); + ffp->m_ofmt_ctx = NULL; + ffp->is_first = 0; + av_log(NULL, AV_LOG_INFO, "===== 文件录制结束成功 =====\n"); + } + + av_log(ffp, AV_LOG_DEBUG, "stopRecord ok\n"); + } else { + av_log(ffp, AV_LOG_ERROR, "don't need stopRecord\n"); + } + pthread_mutex_unlock(&ffp->record_mutex); + pthread_mutex_destroy(&ffp->record_mutex); + return 0; +} + + +//保存文件 +int ffp_record_file(FFPlayer *ffp, AVPacket *packet) +{ + pthread_mutex_lock(&ffp->record_mutex); + assert(ffp); + VideoState *is = ffp->is; + int ret = 0; + AVStream *in_stream; + AVStream *out_stream; + + if (ffp->is_record) { + if (packet == NULL) { + ffp->record_error = 1; + av_log(ffp, AV_LOG_ERROR, "packet == NULL"); + pthread_mutex_unlock(&ffp->record_mutex); + return -1; + } + + AVPacket *pkt = (AVPacket *)av_malloc(sizeof(AVPacket)); // 与看直播的 AVPacket分开,不然卡屏 + av_new_packet(pkt, 0); +// av_packet_alloc(); +// av_packet_from_data(pkt,packet->data,packet->size); + if(packet!=NULL && packet->size>0 && packet->data!=NULL){ + if (0 == av_packet_ref(pkt, packet)) { + av_log(ffp, AV_LOG_INFO, "ffp->start_pts:%"PRId64"",ffp->start_pts); + av_log(ffp, AV_LOG_INFO, "ffp->start_dts:%"PRId64"",ffp->start_dts); + + av_log(ffp, AV_LOG_INFO, "ffp->is_first:%d",ffp->is_first); + if (!ffp->is_first) { // 录制的第一帧,时间从0开始 + if(pkt->flags==AV_PKT_FLAG_KEY){ + //取到I帧开始写录制数据 + ffp->is_first = 1; + ffp->start_pts = pkt->pts; + ffp->start_dts = pkt->dts; + pkt->pts = 0; + pkt->dts = 0; + }else{ + ffp->is_first = 0; + ffp->start_pts = pkt->pts; + ffp->start_dts = pkt->dts; + pkt->pts = 0; + pkt->dts = 0; + av_packet_unref(pkt); + pthread_mutex_unlock(&ffp->record_mutex); + return 0; + } + + } else { // 之后的每一帧都要减去,点击开始录制时的值,这样的时间才是正确的 + pkt->pts = llabs(pkt->pts - ffp->start_pts); + pkt->dts = llabs(pkt->dts - ffp->start_dts); + } + + if(pkt->ptsdts) + pkt->dts = pkt->pts; + if(pkt->dtspts) + pkt->pts = pkt->dts; + in_stream = is->ic->streams[pkt->stream_index]; + out_stream = ffp->m_ofmt_ctx->streams[pkt->stream_index]; + + // 转换PTS/DTS + pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); + pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); + pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base); + + pkt->pos = -1; + + + av_log(ffp, AV_LOG_INFO, "inner pkt->pts:%"PRId64"\n",pkt->pts); + av_log(ffp, AV_LOG_INFO, "inner pkt->dts:%"PRId64"\n",pkt->dts); + av_log(ffp, AV_LOG_INFO, "inner pkt->duration:%"PRId64"\n",pkt->duration); + + + av_log(ffp, AV_LOG_INFO, "av_interleaved_write_frame\n"); + // 写入一个AVPacket到输出文件 + if ((ret = av_interleaved_write_frame(ffp->m_ofmt_ctx, pkt)) < 0) { + av_log(ffp, AV_LOG_ERROR, "Error muxing packet\n"); + } + av_packet_unref(pkt); + pthread_mutex_unlock(&ffp->record_mutex); + } else { + av_log(ffp, AV_LOG_ERROR, "av_packet_ref == NULL"); + } + } + + } + pthread_mutex_unlock(&ffp->record_mutex); + return ret; +} IjkMediaMeta *ffp_get_meta_l(FFPlayer *ffp) { diff --git a/ijkmedia/ijkplayer/ff_ffplay.h b/ijkmedia/ijkplayer/ff_ffplay.h index 536c67c151..6e3770d345 100644 --- a/ijkmedia/ijkplayer/ff_ffplay.h +++ b/ijkmedia/ijkplayer/ff_ffplay.h @@ -116,6 +116,10 @@ float ffp_get_property_float(FFPlayer *ffp, int id, float default_value); void ffp_set_property_float(FFPlayer *ffp, int id, float value); int64_t ffp_get_property_int64(FFPlayer *ffp, int id, int64_t default_value); void ffp_set_property_int64(FFPlayer *ffp, int id, int64_t value); +//录制相关 +int ffp_start_record(FFPlayer *ffp, const char *file_name); +int ffp_stop_record(FFPlayer *ffp); +int ffp_record_file(FFPlayer *ffp, AVPacket *packet); // must be freed with free(); struct IjkMediaMeta *ffp_get_meta_l(FFPlayer *ffp); diff --git a/ijkmedia/ijkplayer/ff_ffplay_def.h b/ijkmedia/ijkplayer/ff_ffplay_def.h index 6f54f7f731..76b8202cab 100755 --- a/ijkmedia/ijkplayer/ff_ffplay_def.h +++ b/ijkmedia/ijkplayer/ff_ffplay_def.h @@ -567,6 +567,15 @@ typedef struct FFPlayer { AVDictionary *player_opts; AVDictionary *swr_opts; AVDictionary *swr_preset_opts; + AVFormatContext *m_ofmt_ctx; // 用于输出的AVFormatContext结构体 + AVOutputFormat *m_ofmt; + pthread_mutex_t record_mutex; // 锁 + int is_record; // 是否在录制 + int record_error; + + int is_first; // 是否第一帧数据 + int64_t start_pts; // 开始录制pts + int64_t start_dts; // 开始录制dts /* ffplay options specified by the user */ #ifdef FFP_MERGE diff --git a/ijkmedia/ijkplayer/ijkplayer.c b/ijkmedia/ijkplayer/ijkplayer.c index dd2a4a8df6..3619ac1833 100755 --- a/ijkmedia/ijkplayer/ijkplayer.c +++ b/ijkmedia/ijkplayer/ijkplayer.c @@ -844,3 +844,29 @@ int ijkmp_get_msg(IjkMediaPlayer *mp, AVMessage *msg, int block) return -1; } + +int ijkmp_start_record(IjkMediaPlayer *mp,const char *file_name) +{ + pthread_mutex_lock(&mp->mutex); + assert(mp); + printf("=====开始录制准备====="); + MPTRACE("ijkmp_startRecord()\n"); + int retval = ffp_start_record(mp->ffplayer,file_name); + printf("=====开始录制已调用=====\n"); + MPTRACE("ijkmp_startRecord()=%d\n", retval); + pthread_mutex_unlock(&mp->mutex); + return retval; +} + +int ijkmp_stop_record(IjkMediaPlayer *mp) +{ + pthread_mutex_lock(&mp->mutex); + assert(mp); + MPTRACE("ijkmp_stopRecord()\n"); + printf("=====结束录制准备=====\n"); + int retval = ffp_stop_record(mp->ffplayer); + printf("=====结束录制已调用=====\n"); + MPTRACE("ijkmp_stopRecord()=%d\n", retval); + pthread_mutex_unlock(&mp->mutex); + return retval; +} diff --git a/ijkmedia/ijkplayer/ijkplayer.h b/ijkmedia/ijkplayer/ijkplayer.h index 58cc8e6f1b..8dbc5d6302 100644 --- a/ijkmedia/ijkplayer/ijkplayer.h +++ b/ijkmedia/ijkplayer/ijkplayer.h @@ -215,6 +215,9 @@ void *ijkmp_get_weak_thiz(IjkMediaPlayer *mp); void *ijkmp_set_weak_thiz(IjkMediaPlayer *mp, void *weak_thiz); void ijkmp_take_snapshot(IjkMediaPlayer *mp); +//录制方法 +int ijkmp_start_record(IjkMediaPlayer *mp,const char *file_name); +int ijkmp_stop_record(IjkMediaPlayer *mp); /* return < 0 if aborted, 0 if no packet and > 0 if packet. */ /* need to call msg_free_res for freeing the resouce obtained in msg */ diff --git a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.h b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.h index 75149d0346..1f2e76f0c0 100644 --- a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.h +++ b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.h @@ -108,6 +108,9 @@ typedef void(^OnSnapshotBlock) (UIImage* __nullable image , NSError* __nullable - (void) addIJKMPEventHandler:(id) handler; - (void) removeIJKMPEventHandler:(id) handler; +- (int) startRecordVideo:(NSString*)fileRealPath; +- (int) stopRecordVideo; + @end NS_ASSUME_NONNULL_END diff --git a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.m b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.m index 31fb5415b0..7fbde9c3d1 100644 --- a/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.m +++ b/ios/IJKMediaPlayer/IJKMediaPlayer/IJKFFMediaPlayer.m @@ -498,4 +498,14 @@ - (void)audioSessionInterrupt:(NSNotification *)notification } } } + +//开始录制 +- (int) startRecordVideo:(NSString*)fileRealPath{ + return ijkmp_start_record(_nativeMediaPlayer, [fileRealPath UTF8String]); +} +//结束录制 +- (int) stopRecordVideo{ + NSLog(@"结束录制"); + return ijkmp_stop_record(_nativeMediaPlayer); +} @end diff --git a/ios/IJKMediaPlayer/universal-framework.sh b/ios/IJKMediaPlayer/universal-framework.sh index 94c838a3f4..0fa9207765 100755 --- a/ios/IJKMediaPlayer/universal-framework.sh +++ b/ios/IJKMediaPlayer/universal-framework.sh @@ -22,6 +22,7 @@ cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME fi # Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory +lipo -remove arm64 "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" -o "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/${TARGET_NAME}" # Step 5. Convenience step to copy the framework to the project's directory