MX 2 лет назад
Родитель
Сommit
d541476c37

+ 4 - 0
non_catalog_apps/video_player/README_CATALOG.md

@@ -0,0 +1,4 @@
+# Video player
+ A Flipper Zero application to play video files (with sound). Uses custom video file format. Is proven to work at 30 FPS and 44100 Hz audio sample rate with full 128 by 64 pixels resolution. Maximum video length is limited only by your SD card capacity (at least it works with 6 hours long video). Supports fast forward, rewind and pause. Obeys device's stealth mode.
+
+See github repo to find the guide for how to convert video files.

+ 5 - 5
non_catalog_apps/video_player/application.fam

@@ -6,11 +6,11 @@ App(
     cdefines=["APP_VIDEO_PLAYER"],
     stack_size=2 * 1024,
     order=90,
-    fap_version=(0, 1),
-    fap_description="An app that plays video along with sound on Flipper Zero.",
-    fap_author="LTVA",
+	fap_version=(0, 2),
+	fap_description="An app that plays video along with sound on Flipper Zero.",
+	fap_author="LTVA",
     fap_weburl="https://github.com/LTVA1/flipper_video_player",
-    fap_icon="video_player.png",
-    fap_icon_assets="images",
+	fap_icon="video_player.png",
+	fap_icon_assets="images",
     fap_category="Media",
 )

+ 10 - 0
non_catalog_apps/video_player/docs/changelog.md

@@ -0,0 +1,10 @@
+# Video Player v0.2 #
+
+## Added ##
+- Rewind and fast forward with right and left arrow keys respectively
+- Obey device's stealth mode
+- If horizontal resolution is less that 128 pixels video is centered on the screen
+
+# Video Player v0.1 #
+
+- Initial release

+ 4 - 0
non_catalog_apps/video_player/init_deinit.c

@@ -61,6 +61,10 @@ void deinit_player(VideoPlayerApp* player) {
         free(player->buffer);
     }
 
+    if(player->fake_audio_buffer) {
+        free(player->fake_audio_buffer);
+    }
+
     furi_pubsub_unsubscribe(player->input, player->input_subscription);
 
     player->canvas = NULL;

BIN
non_catalog_apps/video_player/screenshots/demo_screen.png


+ 196 - 105
non_catalog_apps/video_player/video_player.c

@@ -4,9 +4,11 @@
 
 #include <video_player_icons.h>
 #include <furi.h>
+#include <furi/core/thread.h>
 #include <furi_hal.h>
 #include <cli/cli.h>
 #include <gui/gui.h>
+#include "furi_hal_rtc.h"
 
 void draw_callback(Canvas* canvas, void* ctx) {
     PlayerViewModel* model = (PlayerViewModel*)ctx;
@@ -74,6 +76,32 @@ bool open_file_stream(Stream* stream) {
     return result;
 }
 
+void draw_progress_bar(VideoPlayerApp* player) {
+    canvas_set_color(player->canvas, ColorWhite);
+    canvas_draw_box(player->canvas, 0, 57, 128, 7);
+    canvas_set_color(player->canvas, ColorBlack);
+    canvas_draw_frame(player->canvas, 0, 58, 128, 6);
+    canvas_draw_box(player->canvas, 1, 59, player->progress, 4);
+}
+
+void draw_all(VideoPlayerApp* player) {
+    canvas_reset(player->canvas);
+
+    canvas_draw_xbm(
+        player->canvas,
+        player->width == 128 ? 0 : (128 - player->width) / 2,
+        0,
+        player->width,
+        player->height,
+        player->image_buffer);
+
+    if(player->seeking) {
+        draw_progress_bar(player);
+    }
+
+    canvas_commit(player->canvas);
+}
+
 int32_t video_player_app(void* p) {
     UNUSED(p);
 
@@ -83,147 +111,210 @@ int32_t video_player_app(void* p) {
     UNUSED(st);
     furi_record_close(RECORD_STORAGE);
 
-    VideoPlayerApp* player = init_player();
-
-    if(open_file_stream(player->stream)) {
-    }
+    bool exit = false;
 
-    else {
-        player->quit = true;
-        //goto end;
-    }
-
-    if(!(player->quit)) {
-        char header[8];
-        header[7] = '\0';
-        stream_read(player->stream, (uint8_t*)header, 7);
-
-        if(strcmp(header, "BND!VID") != 0) {
+    while(!exit) {
+        VideoPlayerApp* player = init_player();
+        if(open_file_stream(player->stream)) {
+            player->quit = false;
+        } else {
             player->quit = true;
-            //goto end;
+            exit = true;
         }
 
-        stream_read(player->stream, (uint8_t*)&player->version, sizeof(player->version));
-        stream_read(player->stream, (uint8_t*)&player->num_frames, sizeof(player->num_frames));
-        stream_read(
-            player->stream, (uint8_t*)&player->audio_chunk_size, sizeof(player->audio_chunk_size));
-        stream_read(player->stream, (uint8_t*)&player->sample_rate, sizeof(player->sample_rate));
-        stream_read(player->stream, &player->height, sizeof(player->height));
-        stream_read(player->stream, &player->width, sizeof(player->width));
-
-        player->buffer = (uint8_t*)malloc(
-            player->audio_chunk_size * 2 + (uint32_t)player->height * (uint32_t)player->width / 8);
-        memset(
-            player->buffer,
-            0,
-            player->audio_chunk_size * 2 + (uint32_t)player->height * (uint32_t)player->width / 8);
-
-        player->image_buffer_length = (uint32_t)player->height * (uint32_t)player->width / 8;
-        player->audio_buffer = (uint8_t*)&player->buffer[player->image_buffer_length];
-        player->image_buffer = player->buffer;
-    }
-
-    if(furi_hal_speaker_acquire(1000)) {
         if(!(player->quit)) {
-            player_init_hardware_and_play(player);
-        }
+            char header[8];
+            header[7] = '\0';
+            stream_read(player->stream, (uint8_t*)header, 7);
 
-        // Текущее событие типа кастомного типа VideoPlayerEvent
-        VideoPlayerEvent event;
+            if(strcmp(header, "BND!VID") != 0) {
+                player->quit = true;
+            }
 
-        //view_dispatcher_switch_to_view(player->view_dispatcher, VIEW_PLAYER);
+            stream_read(player->stream, (uint8_t*)&player->version, sizeof(player->version));
+            stream_read(player->stream, (uint8_t*)&player->num_frames, sizeof(player->num_frames));
+            stream_read(
+                player->stream,
+                (uint8_t*)&player->audio_chunk_size,
+                sizeof(player->audio_chunk_size));
+            stream_read(
+                player->stream, (uint8_t*)&player->sample_rate, sizeof(player->sample_rate));
+            stream_read(player->stream, &player->height, sizeof(player->height));
+            stream_read(player->stream, &player->width, sizeof(player->width));
+
+            player->header_size = stream_tell(player->stream);
+
+            player->buffer = (uint8_t*)malloc(
+                player->audio_chunk_size * 2 +
+                (uint32_t)player->height * (uint32_t)player->width / 8);
+            memset(
+                player->buffer,
+                0,
+                player->audio_chunk_size * 2 +
+                    (uint32_t)player->height * (uint32_t)player->width / 8);
+
+            player->image_buffer_length = (uint32_t)player->height * (uint32_t)player->width / 8;
+            player->audio_buffer = (uint8_t*)&player->buffer[player->image_buffer_length];
+            player->image_buffer = player->buffer;
+
+            player->fake_audio_buffer = (uint8_t*)malloc(player->audio_chunk_size * 2);
+
+            player->frame_size =
+                player->audio_chunk_size + player->image_buffer_length; //for seeking
+            player->frames_per_turn = player->num_frames / 126;
+
+            player->silent = furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode);
+        }
 
-        //switch from view dispatcher to direct draw
-        view_dispatcher_remove_view(player->view_dispatcher, VIEW_PLAYER);
+        if(furi_hal_speaker_acquire(1000)) {
+            if(!(player->quit)) {
+                player_init_hardware_and_play(player);
+            }
 
-        view_dispatcher_free(player->view_dispatcher);
+            // Текущее событие типа кастомного типа VideoPlayerEvent
+            VideoPlayerEvent event;
 
-        player_view_free(player->player_view);
-        furi_record_close(RECORD_GUI);
+            //view_dispatcher_switch_to_view(player->view_dispatcher, VIEW_PLAYER);
 
-        player->input = furi_record_open(RECORD_INPUT_EVENTS);
-        player->gui = furi_record_open(RECORD_GUI);
-        player->canvas = gui_direct_draw_acquire(player->gui);
+            //switch from view dispatcher to direct draw
+            view_dispatcher_remove_view(player->view_dispatcher, VIEW_PLAYER);
 
-        player->input_subscription =
-            furi_pubsub_subscribe(player->input, direct_input_callback, player);
+            view_dispatcher_free(player->view_dispatcher);
 
-        if(player->quit) {
-            deinit_player(player);
-            player_deinit_hardware();
-            return 0;
-        }
+            player_view_free(player->player_view);
+            furi_record_close(RECORD_GUI);
 
-        player->playing = true;
+            player->input = furi_record_open(RECORD_INPUT_EVENTS);
+            player->gui = furi_record_open(RECORD_GUI);
+            player->canvas = gui_direct_draw_acquire(player->gui);
 
-        furi_thread_set_current_priority(FuriThreadPriorityIdle);
+            player->input_subscription =
+                furi_pubsub_subscribe(player->input, direct_input_callback, player);
 
-        while(!(player->quit)) {
-            furi_check(
-                furi_message_queue_get(player->event_queue, &event, FuriWaitForever) ==
-                FuriStatusOk);
+            if(player->quit) {
+                deinit_player(player);
+                player_deinit_hardware();
+                return 0;
+            }
 
-            if(event.type == EventTypeInput) {
-                if(event.input.key == InputKeyBack) {
-                    player->quit = true;
+            player->playing = true;
+
+            //vTaskPrioritySet(furi_thread_get_current_id(), FuriThreadPriorityIdle);
+            furi_thread_set_current_priority(FuriThreadPriorityIdle);
+
+            while(!(player->quit)) {
+                furi_check(
+                    furi_message_queue_get(player->event_queue, &event, FuriWaitForever) ==
+                    FuriStatusOk);
+
+                if(event.type == EventTypeInput) {
+                    if(event.input.key == InputKeyBack) {
+                        player->quit = true;
+                    }
+
+                    if(event.input.key == InputKeyOk) {
+                        player->playing = !player->playing;
+                    }
+
+                    if(event.input.key == InputKeyLeft) {
+                        player->seeking = true;
+                        int32_t seek = CLAMP(
+                            (int32_t)stream_tell(player->stream) -
+                                player->frames_per_turn * player->frame_size,
+                            (int32_t)player->num_frames * player->frame_size + player->header_size,
+                            player->header_size);
+                        stream_seek(player->stream, seek, StreamOffsetFromStart);
+
+                        player->progress = (uint8_t)((int64_t)stream_tell(player->stream) * (int64_t)126 / ((int64_t)player->num_frames * (int64_t)player->frame_size + (int64_t)player->header_size));
+
+                        if(event.input.type == InputTypeRelease) {
+                            player->seeking = false;
+                        }
+
+                        static VideoPlayerEvent event = {.type = EventTypeJustRedraw};
+                        furi_message_queue_put(player->event_queue, &event, 0);
+                    }
+
+                    if(event.input.key == InputKeyRight) {
+                        player->seeking = true;
+                        int32_t seek = CLAMP(
+                            (int32_t)stream_tell(player->stream) +
+                                player->frames_per_turn * player->frame_size,
+                            (int32_t)player->num_frames * player->frame_size + player->header_size,
+                            player->header_size);
+                        stream_seek(player->stream, seek, StreamOffsetFromStart);
+
+                        player->progress = (uint8_t)((int64_t)stream_tell(player->stream) * (int64_t)126 / ((int64_t)player->num_frames * (int64_t)player->frame_size + (int64_t)player->header_size));
+
+                        if(event.input.type == InputTypeRelease) {
+                            player->seeking = false;
+                        }
+
+                        static VideoPlayerEvent event = {.type = EventTypeJustRedraw};
+                        furi_message_queue_put(player->event_queue, &event, 0);
+                    }
+
+                    if(player->playing) {
+                        player_start();
+                    }
+
+                    else {
+                        player_stop();
+                    }
                 }
 
-                if(event.input.key == InputKeyOk) {
-                    player->playing = !player->playing;
-                }
+                if(event.type == EventType1stHalf) {
+                    uint8_t* audio_buffer = player->audio_buffer;
 
-                if(player->playing) {
-                    player_start();
-                }
+                    stream_read(player->stream, player->image_buffer, player->image_buffer_length);
 
-                else {
-                    player_stop();
-                }
-            }
+                    if(player->silent) {
+                        stream_read(
+                            player->stream, player->fake_audio_buffer, player->audio_chunk_size);
+                    }
 
-            if(event.type == EventType1stHalf) {
-                //reading image+sound data in one pass since in this case image buffer and first part of audio buffer are continuous chunk of memory; should probably improve FPS
-                stream_read(
-                    player->stream,
-                    player->image_buffer,
-                    player->image_buffer_length + player->audio_chunk_size);
+                    else {
+                        stream_read(player->stream, audio_buffer, player->audio_chunk_size);
+                    }
 
-                player->frames_played++;
+                    player->frames_played++;
 
-                canvas_reset(player->canvas);
+                    draw_all(player);
+                }
 
-                canvas_draw_xbm(
-                    player->canvas, 0, 0, player->width, player->height, player->image_buffer);
+                if(event.type == EventType2ndHalf) {
+                    uint8_t* audio_buffer = &player->audio_buffer[player->audio_chunk_size];
 
-                canvas_commit(player->canvas);
-            }
+                    stream_read(player->stream, player->image_buffer, player->image_buffer_length);
 
-            if(event.type == EventType2ndHalf) {
-                uint8_t* audio_buffer = &player->audio_buffer[player->audio_chunk_size];
+                    if(player->silent) {
+                        stream_read(
+                            player->stream, player->fake_audio_buffer, player->audio_chunk_size);
+                    }
 
-                stream_read(player->stream, player->image_buffer, player->image_buffer_length);
-                stream_read(player->stream, audio_buffer, player->audio_chunk_size);
+                    else {
+                        stream_read(player->stream, audio_buffer, player->audio_chunk_size);
+                    }
 
-                player->frames_played++;
+                    player->frames_played++;
 
-                canvas_reset(player->canvas);
+                    draw_all(player);
+                }
 
-                canvas_draw_xbm(
-                    player->canvas, 0, 0, player->width, player->height, player->image_buffer);
+                if(event.type == EventTypeJustRedraw) {
+                    draw_all(player);
+                }
 
-                canvas_commit(player->canvas);
-            }
+                if(player->frames_played == player->num_frames) {
+                    player->quit = true;
+                }
 
-            if(player->frames_played == player->num_frames) {
-                player->quit = true;
+                furi_thread_yield();
             }
-
-            furi_thread_yield();
         }
+        deinit_player(player);
+        player_deinit_hardware();
     }
 
-    deinit_player(player);
-    player_deinit_hardware();
-
     return 0;
 }

+ 13 - 0
non_catalog_apps/video_player/video_player.h

@@ -21,6 +21,7 @@ typedef enum {
     EventTypeInput,
     EventType1stHalf,
     EventType2ndHalf,
+    EventTypeJustRedraw,
 } EventType;
 
 typedef struct {
@@ -55,6 +56,8 @@ typedef struct {
     uint8_t* audio_buffer;
     uint8_t* image_buffer;
 
+    uint8_t* fake_audio_buffer; //actually not connected to any sound routine
+
     uint8_t* buffer;
 
     uint32_t num_frames;
@@ -69,9 +72,19 @@ typedef struct {
 
     uint32_t frames_played;
 
+    int32_t frame_size;
+    int32_t header_size; //for seeking
+    int32_t frames_per_turn; //frames / 126, how many frames to wind forwards/backwards when seeking
+
+    uint8_t progress;
+
     bool playing;
 
     bool quit;
+
+    bool seeking; //to display progress bar
+
+    bool silent;
 } VideoPlayerApp;
 
 typedef struct {