All Products
Search
Document Center

:iOS

Last Updated:Jun 20, 2026

This topic explains how to integrate the Native RTS SDK with a third-party, FFmpeg-based player on iOS to implementRTS. This guide uses ijkplayer (tag k0.8.8) as an example.

Prerequisites

You must have compiled the ijkplayer source code. For instructions, see the README.md file in the ijkplayer repository.

Procedure

  1. Download and decompress the ijkplayer source code. For the download link, see ijkplayer.

  2. Download and decompress the Native RTS SDK. For the download link, see Release notes.

  3. Integrate the Native RTS SDK into ijkplayer as a plug-in. There are two integration methods:

    Integration method

    Description

    Advantage

    Disadvantage

    Extend FFmpeg.

    Extend the FFmpeg demuxer in ijkplayer.

    Easier to use. No special logic is required to handle ARTC URLs.

    Requires recompiling the FFmpeg library.

    Extend ijkplayer.

    Add an AVInputFormat to ijkplayer.

    Does not require compiling FFmpeg.

    Requires adding custom logic to ff_ffplay.c.

    FFmpeg extension

    1. In the root directory of ijkplayer, run the ./init-ios.sh command to initialize the project.

      Xcode 14 no longer supports the armv7, i386, and x86_64 architectures. You must remove them from the init-ios.sh file.

      The following example shows the compilation script for Xcode 14:

      # FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64"
      FF_ALL_ARCHS_IOS11_SDK="arm64"
      FF_ALL_ARCHS=$FF_ALL_ARCHS_IOS11_SDK
    2. In the ios directory of ijkplayer, run ./compile-ffmpeg.sh clean if a ffmpeg-$arch directory exists in the build directory from a previous compilation.

    3. Copy the rtsdec.c file from the Native RTS SDK to the ijkplayer/ios/ffmpeg-$arch/libavformat directory.

    4. In ijkplayer/ios/ffmpeg-$arch/libavformat/Makefile, add rtsdec.o to the OBJS list to compile the rtsdec.c file:

      --- a/libavformat/Makefile
      +++ b/libavformat/Makefile
      @@ -24,6 +24,7 @@ OBJS = allformats.o          \
                 sdp.o                    \
                 url.o                    \
                 utils.o                  \
      +          rtsdec.o                 \

    5. Modify the allformats.c file.

      Modify ijkplayer/ios/ffmpeg-$arch/libavformat/allformats.c to enable the ARTC protocol by default.

      diff --git a/libavformat/allformats.c b/libavformat/allformats.c
      index 405ddb5ad9f..a136a7836b0 100644
      --- a/libavformat/allformats.c
      +++ b/libavformat/allformats.c
      @@ -385,6 +385,8 @@ static void register_all(void)
           REGISTER_DEMUXER (LIBGME,            libgme);
           REGISTER_DEMUXER (LIBMODPLUG,        libmodplug);
           REGISTER_DEMUXER (LIBOPENMPT,        libopenmpt);
      +    extern AVInputFormat ff_rtc_demuxer;
      +    av_register_input_format(&ff_rtc_demuxer);
       }
    6. Modify the FFmpeg compilation script ijkplayer/config/module-lite.sh to enable PCM decoding. The Native RTS SDK outputs PCM data.

      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16be_planar"
      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16le"
      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16le_planar"

      Optional: To support streams over HTTPS, add OpenSSL support to the file.

      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-openssl"
    7. Compile.

      In the ijkplayer/ios directory, run the ./compile-ffmpeg.sh all script. As described in step a, remove the unsupported architectures from the script. After the compilation is complete, verify that the FFmpeg output files are present in the ijkplayer/ios/build/universal directory.

      Optional: To support streams over HTTPS, first run the ./compile-openssl.sh all script. As described in step a, remove the unsupported architectures from the script. This script generates libssl.a and libcrypto.a in the ijkplayer/ios/build/universal/lib directory. Then, run ./compile-ffmpeg.sh all.

    8. Copy the RtsSDK.framework file from the Native RTS SDK to the ijkplayer/ios/IJKMediaDemo/IJKMediaDemo directory.

    9. Open ios/IJKMediaDemo/IJKMediaDemo.xcodeproj in Xcode.

    10. Add the RtsSDK.framework dependency. In your Xcode project, under TARGETS, select IJKMediaFramework and open its General tab. In the Frameworks and Libraries section, add RtsSDK.framework and set its Embed option to Do Not Embed.

    11. Copy the Native RTS SDK header files.

      Copy the header files rts_api.h and rts_messages.h from the Native RTS SDK to the ijkplayer/ios/build/universal/lib directory.

    12. Add the RTS logic to ff_ffplay.c.

      Import the header file rts_api.h.

      #include "rts_api.h"

      Modify the ijkplayer/ijkmedia/ijkplayer/ff_ffplay.c file to set the AVInputFormat function pointer for ARTC.

      extern AVInputFormat ff_rtc_demuxer;
      extern int artc_reload(AVFormatContext *ctx);
      extern void av_set_rts_demuxer_funcs(const struct rts_glue_funcs *funcs);
      extern void artc_set_rts_param(char* key, char* value);
      extern long long artc_get_state(AVFormatContext *ctx, int key);
      
      int version = 2;
      const struct rts_glue_funcs* rts_funcs = get_rts_funcs(version);
      // set to ffmpeg plugin
      av_set_rts_demuxer_funcs(rts_funcs);
      artc_set_rts_param((char*)"AutoReconnect", (char*)"false");

    ijkplayer extension

    1. In the root directory of ijkplayer, run the ./init-ios.sh command to initialize the project.

      Xcode 14 no longer supports the armv7, i386, and x86_64 architectures. You must remove them from the init-ios.sh file.

      The following example shows the compilation script for Xcode 14:

      # FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64"
      FF_ALL_ARCHS_IOS11_SDK="arm64"
      FF_ALL_ARCHS=$FF_ALL_ARCHS_IOS11_SDK
    2. In the ios directory of ijkplayer, run ./compile-ffmpeg.sh clean if a ffmpeg-$arch directory exists in the build directory from a previous compilation.

    3. Run the FFmpeg compilation script ijkplayer/config/module-lite.sh to enable PCM decoding. The Native RTS SDK outputs PCM data.

      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16be_planar"
      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16le"
      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=pcm_s16le_planar"

      Optional: To support streams over HTTPS, add OpenSSL support to the file.

      export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-openssl"
    4. Compile.

      In the ijkplayer/ios directory, run the ./compile-ffmpeg.sh all script. As described in step a, remove the unsupported architectures from the script. After the compilation is complete, verify that the FFmpeg output files are present in the ijkplayer/ios/build/universal directory.

      Optional: To support streams over HTTPS, first run the ./compile-openssl.sh all script. As described in step a, remove the unsupported architectures from the script. This script generates libssl.a and libcrypto.a in the ijkplayer/ios/build/universal/lib directory. Then, run ./compile-ffmpeg.sh all.

    5. Copy the RtsSDK.framework file from the Native RTS SDK to the ijkplayer/ios/IJKMediaDemo/IJKMediaDemo directory.

    6. Open the IJKMediaDemo project in Xcode. Add the rtsdec.c file (from the Native RTS SDK) and ijkiourlhook.c file to the ijkavformat directory. In General > Frameworks, Libraries, and Embedded Content, add the required framework dependencies, including RtsSDK.framework, AudioToolbox.framework, AVFoundation.framework, CoreMedia.framework, CoreVideo.framework, IJKMediaFramework.framework, VideoToolbox.framework, OpenGLES.framework, MediaPlayer.framework, libz.tbd, libbz2.tbd, and libstdc++.tbd.

      Copy the header files rts_api.h and rts_messages.h from the Native RTS SDK to the ijkplayer/ios/build/universal/lib directory.

    7. Add the RTS logic to ff_ffplay.c.

      Import the header file rts_api.h.

      #include "rts_api.h"

      Modify the ijkplayer/ijkmedia/ijkplayer/ff_ffplay.c file to set the AVInputFormat function pointer for ARTC.

      if(strncmp(is->filename, "artc://", 7) == 0) {
          extern AVInputFormat ff_rtc_demuxer;
        extern int artc_reload(AVFormatContext *ctx);
        extern void av_set_rts_demuxer_funcs(const struct rts_glue_funcs *funcs);
        extern void artc_set_rts_param(char* key, char* value);
        extern long long artc_get_state(AVFormatContext *ctx, int key);
      
        int version = 2;
        const struct rts_glue_funcs* rts_funcs = get_rts_funcs(version);
        // set to ffmpeg plugin
        av_set_rts_demuxer_funcs(rts_funcs);
        artc_set_rts_param((char*)"AutoReconnect", (char*)"false");
        is->iformat = &ff_rtc_demuxer;
      }
      else {
        if(ffp->iformat_name)
          is->iformat = av_find_input_format(ffp->iformat_name);
      }
  4. Compile the IJKMediaPlayer project.

    After completing the steps for either the FFmpeg extension or ijkplayer extension method, build the ijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj project using the Archive action. This action generates the IJKMediaFramework.framework file in the Products directory.

    Optional: To support streams over HTTPS, you must import the previously generated libssl.a and libcrypto.a files from the ijkplayer/ios/build/universal/lib directory. In the project's Build Phases, add them to Link Binary With Libraries before you compile.

  5. Import the RtsSDK.framework from the Native RTS SDK and the IJKMediaFramework.framework from ijkplayer into your custom project.

    In your custom Xcode project, drag the RtsSDK.framework and IJKMediaFramework.framework files into the project directory and add them to your target's dependency libraries.

  6. Call ijkplayer APIs to implement theRTS feature.

    • Create the ijkplayer instance

      // Generate an ARTC protocol URL
      _url = @"artc://xxxx";
      
      // Create a custom playerView
      [self.view addSubview:self.playerView];
      ...
      
      IJKFFOptions *options = [IJKFFOptions optionsByDefault];
      // Hardware decoding
      [options setPlayerOptionIntValue:1 forKey:@"videotoolbox"];
      // Software decoding
      //[options setPlayerOptionIntValue:0 forKey:@"videotoolbox"];
      
      _ijkPlayer = [[IJKFFMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:_url] withOptions:options];
      _ijkPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
      _ijkPlayer.view.frame = self.playerView.bounds;
      _ijkPlayer.scalingMode = IJKMPMovieScalingModeAspectFit; // Scaling mode
      _ijkPlayer.shouldAutoplay = YES; // Enable autoplay
      [self.playerView addSubview:_ijkPlayer.view];
    • Control playback

      • Start playback.

        Playback must be started on the main thread. Before restarting playback, explicitly stop and release the player.

        dispatch_async(dispatch_get_main_queue(), ^{
            [_ijkPlayer prepareToPlay];
            [_ijkPlayer play];
        });
      • Stop playback and release the player.

        // Stop playback
        [_ijkPlayer stop];
        
        // Release the player
        [_ijkPlayer shutdown];
        _ijkPlayer = nil;

Listen for RTS events

  • Listen for RTS message callbacks

    Open the ijkplayer/ios/IJKMediaPlayer/IJKMediaPlayer.xcodeproj project and add the necessary adaptation methods to the relevant files.

    1. Modify the ff_ffplay.c file by inserting the following code block after the static int audio_open(FFPlayer *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params){} method.

      extern int artcDemuxerMessage(struct AVFormatContext *s, int type, void *data, size_t data_size);
      // Aliyun RTS: Receive ARTC message
      int onArtcDemuxerMessage(struct AVFormatContext *s, int type, void *data, size_t data_size)
      {
          return artcDemuxerMessage(s, type, data, data_size);
      }
      
      int artcDemuxerMessage(struct AVFormatContext *s, int type, void *data, size_t data_size)
      {
          // Aliyun RTS: Send message to the app
          FFPlayer *ffp = (FFPlayer *)s->opaque;
          const char *data_msg = (const char *)data;
          ffp_notify_msg4(ffp,FFP_MSG_ARTC_DIRECTCOMPONENTMSG,type,0,data_msg,data_size);
          return 0;
      }
    2. Modify the ff_ffplay.c file by inserting the following RTS code block into the static int is_realtime(AVFormatContext *s){} method.

      static int is_realtime(AVFormatContext *s)
      {
          if(   !strcmp(s->iformat->name, "rtp")
             || !strcmp(s->iformat->name, "rtsp")
             || !strcmp(s->iformat->name, "sdp")
             // *** RTS code block begin ***
             || !strcmp(s->iformat->name, "artc")
             // *** RTS code block end ***
          )
              return 1;
      
          if(s->pb && (   !strncmp(s->filename, "rtp:", 4)
                       || !strncmp(s->filename, "udp:", 4)
                      )
          )
              return 1;
          return 0;
       }                           
    3. Modify the ff_ffplay.c file by inserting the following RTS code blocks into the static int read_thread(void *arg){} method.

      static int read_thread(void *arg)
      {
        ......
        ic = avformat_alloc_context();
        if (!ic) {
          av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
          ret = AVERROR(ENOMEM);
          goto fail;
        }
      
        // *** RTS code block begin ***
        ic->opaque = ffp;
        ic->control_message_cb = onArtcDemuxerMessage;
        // *** RTS code block end ***
      
        ......
        if (ffp->skip_calc_frame_rate) {
           av_dict_set_int(&ic->metadata, "skip-calc-frame-rate", ffp->skip_calc_frame_rate, 0);
           av_dict_set_int(&ffp->format_opts, "skip-calc-frame-rate", ffp->skip_calc_frame_rate, 0);
        }
      
        // *** RTS code block begin ***
        if(strncmp(is->filename, "artc://", 7) == 0) {
           extern AVInputFormat ff_rtc_demuxer;
           is->iformat = &ff_rtc_demuxer;
        } else {
           if(ffp->iformat_name)
             is->iformat = av_find_input_format(ffp->iformat_name);
         }
        // *** RTS code block end ***
        ......
        pkt->flags = 0;
        // *** RTS code block begin ***
        if(strncmp(is->filename, "artc://", 7) == 0) {
            bool videoExist = is->video_stream >= 0;
          bool audioExist = is->audio_stream >= 0;
          // av_log(NULL, AV_LOG_INFO, "videoDuration %lld audioDuration %lld rate %f videoframeQue %d audioFrameque %d\n",
          // is->videoq.duration, is->audioq.duration, ffp->pf_playback_rate,
          // frame_queue_nb_remaining(&is->pictq), frame_queue_nb_remaining(&is->sampq));
          if(!videoExist) {
             if(is->audioq.duration > 300 ) { // accelerate
                 if(ffp->pf_playback_rate <= 1.0) {
                     ffp->pf_playback_rate = 1.3;
                     ffp->pf_playback_rate_changed = 1;
                     av_log(NULL, AV_LOG_INFO, "aliyun rts set rate to %f\n", ffp->pf_playback_rate);
                 }
             }
             else if(is->audioq.duration < 200) { // restore speed
                 if(ffp->pf_playback_rate > 1.0) {
                     ffp->pf_playback_rate = 1.0;
                     ffp->pf_playback_rate_changed = 1;
                     av_log(NULL, AV_LOG_INFO, "aliyun rts restore rate 1.0\n");
                  }
             }
          }
          else if((!videoExist || (videoExist && is->videoq.duration > 300)) && (!audioExist || (audioExist && is->audioq.duration > 300))) {
             if(ffp->pf_playback_rate <= 1) {
                 ffp->pf_playback_rate = 1.3;
                 ffp->pf_playback_rate_changed = 1;
                 av_log(NULL, AV_LOG_INFO, "aliyun rts set rate 1.1\n");
             }
          } else if((videoExist && is->videoq.duration <= 100) ||  (audioExist && is->audioq.duration <= 100)){
             if(ffp->pf_playback_rate > 1) {
                 ffp->pf_playback_rate = 1;
                 ffp->pf_playback_rate_changed = 1;
                 av_log(NULL, AV_LOG_INFO, "aliyun rts set rate 1\n");
              }
          }
        }
        // *** RTS code block end ***
        ......
      }
    4. Modify the ff_ffmsg.h file by adding the interface declaration for RTS messages.

      #define FFP_MSG_ARTC_DIRECTCOMPONENTMSG     3000
    5. Send RTS messages to the upper layer.

      • Modify the IJKMediaPlayback.h file to add the name declaration for the RTS message listener.

        IJK_EXTERN NSString *const IJKMPMoviePlayerRtsMsgNotification;
      • Modify the IJKMediaPlayback.m file to add the name definition for the RTS message listener.

        NSString *const IJKMPMoviePlayerRtsMsgNotification = @"IJKMPMoviePlayerRtsMsgNotification";
      • Modify the IJKFFMoviePlayerController.m file to add handling for the RTS message listener within the postEvent: method.

        - (void)postEvent: (IJKFFMoviePlayerMessage *)msg
        {
            ......
          case FFP_MSG_ARTC_DIRECTCOMPONENTMSG:{
             NSString *rtsMsg = [[NSString alloc] initWithUTF8String:avmsg->obj];
             int type = avmsg->arg1;
             if (!rtsMsg) {
                rtsMsg = @"";
             }
             NSDictionary *dic = @{@"type":@(type),@"msg":rtsMsg};
             [[NSNotificationCenter defaultCenter] postNotificationName:IJKMPMoviePlayerRtsMsgNotification
                            object:dic];
             break;
            }
           default:
             // NSLog(@"unknown FFP_MSG_xxx(%d)\n", avmsg->what);
             break;
        }
      • Build the IJKMediaPlayer.xcodeproj project using the Archive action to generate the latest IJKMediaFramework.framework.

  • Add an RTS retry method

    1. Modify the ff_ffplay.h file to add the definition of the rts_reload_flag variable.

      int       rts_reload_flag;
    2. Modify the ff_ffplay.c file by inserting the following code block after the static int audio_open(FFPlayer *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params){} method.

      extern int artc_reload(AVFormatContext *ctx);
      
      void ffp_rts_reload(FFPlayer *ffp){
         if(rts_reload_flag == 0)
         {
            rts_reload_flag = 1;
         }
      }
    3. Modify the ff_ffplay.c file by inserting the following RTS code block into the static int read_thread(void *arg){} method.

      static int read_thread(void *arg)
      {
           ......
      #ifdef FFP_MERGE
         if (is->paused != is->last_paused) {
            is->last_paused = is->paused;
            if (is->paused)
               is->read_pause_return = av_read_pause(ic);
             else
                av_read_play(ic);
         }
      #endif
         // *** RTS code block begin ***
         if(rts_reload_flag){
              rts_reload_flag = 0;
              av_log(ffp, AV_LOG_ERROR, "param  == ffp_rts_reload\n");
              VideoState *is = ffp->is;
              AVFormatContext *ic = is->ic;
              artc_reload(ic);
          }
         // *** RTS code block end ***
         ......
      }
    4. Modify the ijkplayer.h file to add the definition of the ijkmp_rts_reload method.

      void            ijkmp_rts_reload(IjkMediaPlayer *mp);
    5. Modify the ijkplayer.c file to implement the ijkmp_rts_reload method.

      void ijkmp_rts_reload(IjkMediaPlayer *mp)
      {
          ffp_rts_reload(mp->ffplayer);
      }
    6. Add the rtsReload interface to the upper layer.

      • Modify the IJKFFMoviePlayerController.h file to add the declaration of the rtsReload method.

        - (void)rtsReload;
      • Modify the IJKFFMoviePlayerController.m file to implement the rtsReload method.

        - (void)rtsReload {
            ijkmp_rts_reload(_mediaPlayer);
        }
      • Build the IJKMediaPlayer.xcodeproj project using the Archive action to generate the latest IJKMediaFramework.framework.

  • Implement stream degradation

    • Listen for the RTS message callback.

      [[NSNotificationCenter defaultCenter] addObserver:self
                                               selector:@selector(reviceMsg:)
                                                   name:IJKMPMoviePlayerRtsMsgNotification
                                                 object:nil];
    • Implement the stream degradation logic in the RTS message callback method.

      Stream degradation is a strategy that changes the player's URL prefix from artc:// to rtmp:// or to the http://xxxx.flv format. The player then updates its source URL to resume playback.

      // Stream degradation to a traditional live streaming URL, such as http://xxx.flv or rtmp://xxx
      - (void)convertArtcToRtmpPlay {
          // Get the current playback URL and extract its prefix
          NSArray *urlSeparated = [self.url componentsSeparatedByString:@"://"];
          NSString *urlPrefix = urlSeparated.firstObject;
          // Check if the URL prefix is "artc". If so, degrade to a traditional live stream
          if ([urlPrefix isEqualToString:@"artc"]) {
              // http://xxx.flv is recommended
              _url = [[@"http://" stringByAppendingString:urlSeparated.lastObject] stringByAppendingString:@".flv"];
              // rtmp://xxx
              // _url = [@"rtmp://" stringByAppendingString:urlSeparated.lastObject];
      
                  // Stop playback and destroy the player
            [_ijkPlayer stop];
            [_ijkPlayer shutdown];
            _ijkPlayer = nil;
      
            // Reset the playback source
            _ijkPlayer = [[IJKFFMoviePlayerController alloc] initWithContentURL:[NSURL URLWithString:_url] withOptions:options];
            ......
                  // Start playback
            dispatch_async(dispatch_get_main_queue(), ^{
                      [_ijkPlayer prepareToPlay];
                      [_ijkPlayer play];
                  });
          }
      }

      You must first import the RtsSDK API.

      #import <RtsSDK/rts_messages.h>

      Then, handle the player event callback.

      -(void)reviceMsg:(NSNotification*)notification{
          NSDictionary *dic = [notification object];
        NSNumber *type = dic[@"type"];
        switch (type.intValue) {
          case E_DNS_FAIL:
          case E_AUTH_FAIL:
          case E_CONN_TIMEOUT:
          case E_SUB_TIMEOUT:
          case E_SUB_NO_STREAM:
          {
            // Degrade the stream
            [self convertArtcToRtmpPlay];
          }
              break;
          case E_STREAM_BROKEN:
          {
             static BOOL retryStartPlay = YES;
             // On the first RTS media timeout, retry playback once. On a subsequent timeout, degrade the stream directly.
             if (retryStartPlay) {
                 dispatch_async(dispatch_get_main_queue(), ^{
                   [_ijkPlayer rtsReload];
                   });
                 retryStartPlay = NO;
              } else {
                 // Degrade the stream
                 [self convertArtcToRtmpPlay];
              }
           }
            break;
          case E_RECV_STOP_SIGNAL:
                  {
                // Stop playback and destroy the player
                [_ijkPlayer stop];
              [_ijkPlayer shutdown];
              _ijkPlayer = nil;
            }
            break;
          default:
            break;
        }
      }