Page Example

Media D2D Streaming Sample Overview

Mobile native

The Media Streamer sample application demonstrates how to add fully functional adaptive media streaming features to an application. It introduces adaptive multimedia streaming scenarios and helps you to include them in your application.

The following figure illustrates the main views of the Media Streamer application.

Figure: Media Streamer views

Media Streamer main view Media Streamer server view, streaming not started Media Streamer server view, streaming started

To access a test scenario, click the applicable item in the main view:

  • Server view (adaptive server functionality)
  • Client view (adaptive client functionality)

The middle image above illustrates the Media Streamer server view when the streaming has not started. The right image shows the streaming in progress.

To return to the main view, click the Back key.

This topic does not cover UI details, except when directly related to multimedia streaming.

Source Files

You can create and view the sample application project, including the source files, in the IDE.

Table: Source files
File name Description
inc/mediastreamer.h This file contains the type definitions used in the application.
inc/mediastreamer_service.h This file contains the function and type definitions used in the C files, especially in the media_streamer_service_server.c and mediastreamer_service_client.c files.
inc/mediastreamer_view.h This file contains the function and type definitions used in the C files, especially in the mediastreamer_view.c file.
res/content This folder contains the multimedia content used in the application.
res/images This folder contains the images used in the application.
res/service This folder contains the predefined service description for the IoTCon framework used in the application.
src/client_view.c This file contains the functions related to Media Streamer instance control and UI in the "Client view" scenario.
src/mediastreamer.c This file contains the functions related to the application life-cycle and event callbacks.
src/mediastreamer_find_server_view.c This file contains the functions related to the "Select server" view and the handles for the server list and client discovery service.
src/mediastreamer_service_client.c This file contains the service discovery functions related to the client functionality implemented with the IoTCon framework.
src/mediastreamer_service_server.c This file contains the service discovery functions related to the server functionality implemented with the IoTCon framework.
src/mediastreamer_view.c This file contains the functions for implementing views and handling events.
src/server_view.c This file contains the functions related to Media Streamer instance control and UI in the "Server view" scenario.

Implementation

To implement the main view:

  1. The create_base_gui() function is called right after the application starts, to create the main view (window, conformant, and naviframe).

    For each sample scenario, create a separate list item and add it to the list located in the main view, by calling the append_client_view() and append_server_view() functions.

    /*
        @brief Routine for creation of list that contains access links
        to available scenarios
    */
    void
    create_base_gui(appdata_s *ad)
    {
        /* Window */
        ad->win = elm_win_util_standard_add(PACKAGE, PACKAGE);
        elm_win_conformant_set(ad->win, EINA_TRUE);
        elm_win_autodel_set(ad->win, EINA_TRUE);
    
        /* Conformant */
        ad->conform = elm_conformant_add(ad->win);
        elm_win_indicator_mode_set(ad->win, ELM_WIN_INDICATOR_SHOW);
        elm_win_indicator_opacity_set(ad->win, ELM_WIN_INDICATOR_OPAQUE);
        evas_object_size_hint_weight_set(ad->conform, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
        elm_win_resize_object_add(ad->win, ad->conform);
        evas_object_show(ad->conform);
    
        ad->naviframe = elm_naviframe_add(ad->conform);
        eext_object_event_callback_add(ad->naviframe, EEXT_CALLBACK_BACK, _naviframe_back_cb, ad);
        elm_object_content_set(ad->conform, ad->naviframe);
        evas_object_show(ad->naviframe);
    
        /* Create a separate view for each scenario */
        ad->menu_list = elm_list_add(ad->naviframe);
        append_client_view(ad);
        append_server_view(ad);
    
        /* Show the window after the base GUI is set up */
        evas_object_show(ad->win);
    }
    
  2. The append_client_view() and append_server_view() functions call the elm_list_item_append() function and set the _menu_click_cb() click event callback. This callback is called each time the user clicks the list.

    The following example is from the server_view.c file, and it is related to the "Server view" scenario. After the user clicks Server view, you set the _create_streamer() smart callback on the naviframe instance, and create the "Server view" view by calling the _create_view() function.

    /*
        @brief This callback is called after click action is performed
        on a list element
    */
    static void
    _menu_click_cb(void *data, Evas_Object *obj, void *event_info)
    {
        appdata_s *ad = data;
    
        evas_object_smart_callback_add(ad->naviframe, "transition,finished", _create_streamer, ad);
        _create_view(ad->naviframe);
        ad->frame_item = elm_naviframe_item_push(ad->naviframe, SERVER_PAGE_NAME, NULL, NULL, p_data.layout, NULL);
        elm_naviframe_item_pop_cb_set(ad->frame_item, _delete_streamer, NULL);
        dlog_print(DLOG_INFO, LOG_TAG, "Selected %s page", SERVER_PAGE_NAME);
    
        Elm_List_Item *selected_item = (Elm_List_Item *)event_info;
        elm_list_item_selected_set(selected_item, EINA_FALSE);
    }
    
    /* @brief Routine is used to add scenario list item to main view list */
    Elm_Object_Item* 
    append_server_view(appdata_s *ad)
    {
        return elm_list_item_append(ad->menu_list, SERVER_PAGE_NAME, NULL, NULL, _menu_click_cb, (void*)ad);
    }
    

Server View

To implement the server view:

  1. All the server information to the view as Evas_Object (elm_entry_entry) labels.

    The following example shows a part of the _create_view() function, used to render server information, such as server IP address.

    /*
       Fill text entry with information about device
       network connection status and IP address
       associated with connections
    */
    elm_entry_entry_set(p_data.wifi_status_label, WIFI_STATUS_TEXT);
    elm_entry_entry_set(p_data.wifi_ip_label, WIFI_IP_TEXT);
    elm_entry_entry_set(p_data.wifi_direct_status_label, WIFI_DIRECT_STATUS_TEXT);
    elm_entry_entry_set(p_data.wifi_direct_ip_label, WIFI_DIRECT_IP_TEXT);
    
    if (_streamer_wifi_get_ip(&wifi_ip) == MEDIA_STREAMER_ERROR_NONE)
        wifi_status = CONNECTION_STATUS_OK;
    elm_entry_entry_append(p_data.wifi_status_label, wifi_status);
    elm_entry_entry_append(p_data.wifi_ip_label, !wifi_ip ? CONNECTION_IP_NON : wifi_ip);
    if (_streamer_direct_get_ip(&wifi_direct_ip) == MEDIA_STREAMER_ERROR_NONE)
        wifi_direct_status = CONNECTION_STATUS_OK;
    elm_entry_entry_append(p_data.wifi_direct_status_label, wifi_direct_status);
    elm_entry_entry_append(p_data.wifi_direct_ip_label, !wifi_direct_ip ? CONNECTION_IP_NON : wifi_direct_ip);
    
    dlog_print(DLOG_INFO, LOG_TAG, "MediaStreamer was created successfully.");
    evas_object_smart_callback_del(ad->naviframe, "transition,finished", _create_streamer);
    
    /* Set IP address, preferring a Wi-Fi Direct® connection */
    if (wifi_direct_ip)
        p_data.server_ip = strdup(wifi_direct_ip);
    else if (wifi_ip)
        p_data.server_ip = strdup(wifi_ip);
    else
        p_data.server_ip = NULL;
    
  2. Create the Media Streamer instance:
    1. The _create_streamer() callback is called after the transition from the main view to the "Server view" view is finished. It creates a Media Streamer instance using the media_streamer_create() function. If no errors occur (MEDIA_STREAMER_ERROR_NONE), you can proceed by creating nodes and adding them to the Media Streamer instance.

      You can use the media_streamer_set_error_cb() function to set the _streamer_error_cb() callback, which is called each time an error occurs during Media Streamer operations.

      /* @brief Routine is used to encapsulate Media Streamer instance creation */
      static void
      _create_streamer(void *data, Evas_Object *obj, void *event_info)
      {
          appdata_s *ad = (appdata_s *)data;
          if (!ad) {
              dlog_print(DLOG_ERROR, LOG_TAG, "Empty data in transition,finished callback.");
      
              return;
          }
      
          /* Create a Media Streamer instance and set the error callback */
          if (media_streamer_create(&p_data.streamer) != MEDIA_STREAMER_ERROR_NONE) {
              dlog_print(DLOG_ERROR, LOG_TAG, "Fail to create media streamer");
      
              return;
          }
          media_streamer_set_error_cb(p_data.streamer, _streamer_error_cb, NULL);
      
    2. After the Media Streamer instance has been created, create a source node with the media_streamer_node_create_src() function. Since the "Server view" scenario assumes that adaptive content is generated from local content (video or audio), the created source is a file source (MEDIA_STREAMER_NODE_SRC_TYPE_FILE). The function parameters are the node type and a pointer to the media_streamer_node_h handle. This handle is used in further operations, and you need to store it separately in an array-like container (such as an array, list, or hash table).

      To use the file source, you must set the URI of the content by calling the media_streamer_node_set_param() function. The URI is a path to the content available for the application.

      After the source node is created and the URI is set, add the node to the Media Streamer instance by calling the media_streamer_node_add() function with 2 parameters: the media_streamer_h and media_streamer_node_h handles.

          /* Get path to video content */
          char res_path[PATH_MAX] = {0,};
          app_get_resource(VIDEO_PATH, res_path, PATH_MAX);
      
          /*
             Create source node of MEDIA_STREAMER_NODE_SRC_TYPE_FILE type
             for reading content from file
          */
          media_streamer_node_h file_src = NULL;
          media_streamer_node_create_src(MEDIA_STREAMER_NODE_SRC_TYPE_FILE, &file_src);
          dlog_print(DLOG_ERROR, LOG_TAG, "Res path %s", res_path);
      
          /* Add created node to Media Streamer instance */
          media_streamer_node_set_param(file_src, MEDIA_STREAMER_PARAM_URI, res_path);
          media_streamer_node_add(p_data.streamer, file_src);
          p_data.nodes[p_data.node_counter++] = file_src;
      
    3. The scenario is incomplete without at least 1 sink node. The sample application has a predefined video file to test adaptive generation playback. Therefore, you need to create a sink node in order to generate content from the file.

      All sink nodes are created with the media_streamer_node_create_sink() function, which is much the same as media_streamer_node_create_src(). To generate adaptive content, you must create a MEDIA_STREAMER_NODE_SINK_TYPE_ADAPTIVE type sink. Then you set the MEDIA_STREAMER_PARAM_SEGMENT_LOCATION and MEDIA_STREAMER_PARAM_PLAYLIST_LOCATION parameters for the node, to point at the generation target location.

          /* Video sink */
          media_streamer_node_h adaptive_sink = NULL;
          media_streamer_node_create_sink(MEDIA_STREAMER_NODE_SINK_TYPE_ADAPTIVE, &adaptive_sink);
          media_streamer_node_set_param(adaptive_sink, MEDIA_STREAMER_PARAM_SEGMENT_LOCATION, DEFAULT_SEGMENT_PATH);
          media_streamer_node_set_param(adaptive_sink, MEDIA_STREAMER_PARAM_PLAYLIST_LOCATION, DEFAULT_PLAYLIST_PATH);
          media_streamer_node_add(p_data.streamer, adaptive_sink);
          p_data.nodes[p_data.node_counter++] = adaptive_sink;
      
    4. After you have added all the required nodes to the Media Streamer instance, you must change the instance state from MEDIA_STREAMER_STATE_IDLE (after creation) to MEDIA_STREAMER_STATE_READY, by calling the media_streamer_prepare() function:
          /* Prepare Media Streamer instance after adding all required nodes */
          media_streamer_prepare(p_data.streamer);
      }
      
  3. In the _create_player_view() function, create a simple button and set a callback to be called after a click event occurs:
    static void
    _create_player_view(Evas_Object *parent)
    {
        /* Other actions */
    
        /* Play/Pause button */
        p_data.pp_btn = elm_button_add(p_data.layout);
        evas_object_size_hint_weight_set(p_data.pp_btn, EVAS_HINT_EXPAND, 0.0);
        evas_object_size_hint_align_set(p_data.pp_btn, EVAS_HINT_FILL, 0.0);
        elm_object_text_set(p_data.pp_btn, LEFT_ALIGN_TEXT(BUTTON_TEXT));
        evas_object_smart_callback_add(p_data.pp_btn, "clicked", _play_clicked_cb, NULL);
    
        /* Set image for button */
        Evas_Object *img = elm_image_add(p_data.pp_btn);
        app_get_resource(IMG_PATH"/play.png", res_path, PATH_MAX);
        elm_image_file_set(img, res_path, NULL);
        elm_object_part_content_set(p_data.pp_btn, "icon", img);
    
        elm_box_pack_end(p_data.layout, p_data.pp_btn);
        evas_object_show(p_data.pp_btn);
    }
    
  4. Within the _play_clicked_cb() callback, manage the Media Streamer instance state transitions:
    • After the user clicks the Play/Pause button, check the Media Streamer instance state by calling the media_streamer_get_state() function with 2 parameters (media_streamer_h handle and a pointer to the media_streamer_state_e variable). After calling the media_streamer_prepare() function, the state is MEDIA_STREAMER_STATE_READY (if no errors have occurred).
    • In the MEDIA_STREAMER_STATE_READY state, call the media_streamer_play() function to start playback (changing the Media Streamer state to MEDIA_STREAMER_STATE_PLAYING). The video starts to render and audio can be heard.
    • After playback has started, the user can click the Play/Pause button again, in which case you must call the media_streamer_pause() function to pause the Media Streamer and set its state to MEDIA_STREAMER_STATE_PAUSED.
    Evas_Object *img;
    char res_path[PATH_MAX] = {0,};
    
    /*
       Here you handle the play button click event based
       on the current Media Streamer state
    */
    media_streamer_state_e state = MEDIA_STREAMER_STATE_NONE;
    media_streamer_get_state(p_data.streamer, &state);
    
    switch (state) {
    case MEDIA_STREAMER_STATE_PLAYING:
        /*
           If current Media Streamer state is MEDIA_STREAMER_STATE_PLAYING,
           try to pause it and change play button icon to "play.png"
        */
        media_streamer_pause(p_data.streamer);
    
        img = elm_object_part_content_get(p_data.pp_btn, "icon");
        app_get_resource(IMG_PATH"/play.png", res_path, PATH_MAX);
        elm_image_file_set(img, res_path, NULL);
        break;
    case MEDIA_STREAMER_STATE_PAUSED:
    case MEDIA_STREAMER_STATE_READY:
        /*
           If current Media Streamer state is MEDIA_STREAMER_STATE_PAUSE or
           MEDIA_STREAMER_STATE_READY, try to start playback, and reset
           and thaw the timer; play button icon is changed too
        */
        media_streamer_play(p_data.streamer);
        /*
           Check that device is connected to network and has an IP address
           Start service notifications for this address
        */
        if (p_data.server_ip) {
            mediastreamer_create_service_server(p_data.server_ip);
            p_data.service_inited = 1;
        }
    
        img = elm_object_part_content_get(p_data.pp_btn, "icon");
        app_get_resource(IMG_PATH"/pause.png", res_path, PATH_MAX);
        elm_image_file_set(img, res_path, NULL);
        break;
    default:
        /* Default action for unknown state is error */
        dlog_print(DLOG_ERROR, LOG_TAG, "Media Streamer can't play from state [%d]", state);
        break;
    }