harmonwood / capacitor-video-player

Capacitor Video Player Plugin
MIT License
116 stars 46 forks source link

Video Url not found error when i try to play local file #142

Closed paulisidore closed 9 months ago

paulisidore commented 11 months ago

I got a Video URL not found error when trying to play a local file on an Android device like this https://localhost/_capacitor_file_/storage/emulated/0/Documents/KSSV/documents/1/videos/ 0-12-2023-31230.mp4

While the file exists in the folder and playback is correct with an external video player.

Also, playing https://brenopolanski.github.io/html5-video-webvtt-example/MIB2.mp4 works fine with my code.

I also tried with the Uri file:// but the reading is not done.

Could I have a specific example for reading local files on Ionic7/Angular ? Your plugin example use only a URL hosted on the internet.

THANKS

jepiqueau commented 11 months ago

@paulisidore in Android file:///storage/emulated/0/Documents/KSSV/documents/1/videos/0-12-2023-31230.mp4 in iOS file:///var/mobile/Documents/1/videos/0-12-2023-31230.mp4

paulisidore commented 11 months ago

Hello @jepiqueau , thank you for your reply, i try the url file:///storage/emulated/0/Documents/KSSV/documents/1/videos/2-12-2023-101222.mp4 like you suggested but still nothing, i got a toast that says file Url not found error. (2-12-2023-101222.mp4 is another video recorded by the phone itself)

jepiqueau commented 11 months ago

@paulisidore you have yo tell me exactely where did you store your video in the Public Documents folder or in the app Documents folder i do not understant what Documents appears two time in your given path

jepiqueau commented 11 months ago

@paulisidore which code did you use to store the video on the device can you share it

paulisidore commented 11 months ago

Hello @jepiqueau , this is the code i use to record and save file:

async recordVideo2(){
    this.videoStream = await navigator.mediaDevices.getUserMedia(
      {
        video: {
          "facingMode": "environment",
        },
        audio: true
      }
    );
    this.captureElement.nativeElement.srcObject = this.videoStream ;

    const options={ mimeType: 'video/webm'};
    this.mediaRecorder=new MediaRecorder(this.videoStream,options);
    let chunks = <any>[];
    this.mediaRecorder.ondataavailable = (event)=>{   
      if (event.data && event.data.size > 0){
        chunks.push(event.data);
      }
    }

    this.mediaRecorder.onstop = async (event)=>{
      const videoBuffer = new Blob (chunks,{type: "video/webm"});
      if (this.videoStream){
        this.videoStream.getAudioTracks().forEach((audio) => {
          audio.stop(); 
        });
        this.videoStream.getVideoTracks().forEach((video)=>{
          video.stop();
          });
        if (this.mediaRecorder){
          this.mediaRecorder.stop();
        }
      } 

      //Save to file
      const videoInfo = await this.saveVideoBlob(videoBuffer) ;
    }

      this.videoStream.getTracks().forEach((track) =>
        track.addEventListener("ended", () => {
          if (this.videoStream){
            this.videoStream.getAudioTracks().forEach((audio) => audio.stop());
            this.videoStream.getVideoTracks().forEach((video)=>{ video.stop()});
            if (this.mediaRecorder){
              this.mediaRecorder.stop();
            }
          }           
        })
      ); 
    this.isRecording = true;
    this.mediaRecorder.start(100);

  }

async saveVideoBlob(videoBlob: Blob){
    var fileN="2-12-2023-101222";
    const base64Data = await this.convertBlobToBase64(videoBlob) as string ;
    var ext='mp4';
    var dossierUtilisateur ="KSSV/documents/1/videos" ;
    var vFileName=dossierUtilisateur+"/"+fileN+'.'+ext ;
    var fileUri =undefined;
    try{
      await Filesystem.writeFile({
        path: vFileName,
        data: base64Data,
        recursive: true,
        directory: Directory.Documents,
      });
    }catch(ex: any){
      console.error(ex);
    }

    let infos= await Filesystem.stat({
      path: vFileName,
      directory: Directory.Documents
    }).then(
      (reponse)=>{
        return reponse ;
      },
      (err)=>{
        console.log(err);
      }
    )

    if (infos){
      fileUri = infos.uri;
    }

    console.log("Video Uri : "+fileUri);
    return fileUri;

  }

convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => {
    const reader =  new FileReader();
    reader.onerror = reject;
    reader.onload = () => {
        resolve(reader.result);
    };
    reader.readAsDataURL(blob);
  });
paulisidore commented 11 months ago

For play local video i use this code:

async playVideo2(localUri: any){
    //this video Url working fine: https://brenopolanski.github.io/html5-video-webvtt-example/MIB2.mp4");
    await this.videoPlayer.initPlayer({
      mode: 'fullscreen',
      url: localUri,
      type: 'video/mp4' ,
      playerId: 'fullscreen',
      componentTag: 'app-home',
    })
  }
jepiqueau commented 11 months ago

@paulisidore sorry to come back late but i was working on another plugin, i used the following

have you look in Android Studio in which folder is your file?

jepiqueau commented 11 months ago

@paulisidore i did this:

import { Injectable } from '@angular/core';
import { Filesystem, Directory, StatOptions, GetUriOptions } from '@capacitor/filesystem';
import { Capacitor } from '@capacitor/core';

export interface Video {
  id: number;
  device: string;
  type: string;
  title: string;
  url: string;
  thumb?: string;
  note: string;
  subtitle?: string;
  stlang?: string;
}

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private videos: Video[] = [
    {
      id: 1,
      type: "mp4",
      device: "all",
      url: "https://brenopolanski.github.io/html5-video-webvtt-example/MIB2.mp4",
      note: "Breno Polanski",
      title: "Test MP4 with Subtitle",
      subtitle: "https://brenopolanski.github.io/html5-video-webvtt-example/MIB2-subtitles-pt-BR.vtt",
      stlang: "es"
    },
    {
      id: 2,
      type: "mp4",
      device: "all",
      url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
      note: "By Blender Foundation",
      thumb: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg",
      title: "Big Buck Bunny"
    },
    {
      id: 3,
      type: "mp4",
      device: "all",
      url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
      note: "By Blender Foundation",
      thumb: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/ElephantsDream.jpg",
      title: "Elephant Dream"
    },
    {
      id: 4,
      type: "mp4",
      device: "all",
      url: "https://media.bernat.ch/videos/2019-self-hosted-videos-subtitles/progressive.mp4",
      note: "Blender Animation Studio",
      thumb: "https://d2pzklc15kok91.cloudfront.net/images/posters/2019-self-hosted-videos-subtitles.3b55d44c736235.jpg",
      title: "327",
      subtitle: "https://media.bernat.ch/videos/2019-self-hosted-videos-subtitles.en.vtt",
      stlang: "en"
    },
    {
      id: 5,
      type: "aws",
      device: "all",
      url: "https://universo-dev-a-m.s3.amazonaws.com/779970/fe774806dbe7ad042c24ce522b7b46594f16c66e",
      note: "Custom",
      title: "AWS video test",
    },
    {
      id: 6,
      type: "hls",
      device: "all",
      url: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
      note: "By Blender Foundation",
      title: "Big Buck Bunny",
    },
    {
      id: 7,
      type: "mpd",
      device: "android",
      url: "https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd",
      note: "",
      title: "MPD video test",
    },
    {
      id: 8,
      type: "webm",
      device: "android",
      url: "https://upload.wikimedia.org/wikipedia/commons/f/f1/Sintel_movie_4K.webm",
      note: "Blender Foundation",
      thumb: "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f1/Sintel_movie_4K.webm/800px--Sintel_movie_4K.webm.jpg?20150505130125",
      title: "Webm video test",
    },
    {
      id: 9,
      type: "mp4",
      device: "android",
      url: "public/assets/video/Bike720.mp4",
      note: "",
      title: "Video from Assets directory",
    },
    {
      id: 10,
      type: "mp4",
      device: "android",
      url: "file:///storage/emulated/0/Documents/Bike720.mp4",
      note: "",
      title: "Video on Documents directory",
    },
    {
      id: 11,
      type: "mp4",
      device: "android",
      url: "file:///sdcard/Download/Bike720.mp4",
      note: "",
      title: "Video on sdcard Download directory",
    },
    {
      id: 12,
      type: "mp4",
      device: "android",
      url: "file:///sdcard/DCIM/Camera/Bike720.mp4",
      note: "",
      title: "Video on sdcard DCIM directory",
    },
    {
      id: 13,
      type: "mp4",
      device: "android",
      url: "file:///sdcard/Documents/KSSV/documents/1/videos/2-12-2023-101222.mp4",
      note: "",
      title: "Video on sdcard issue#142 directory",
    },
    {
      id: 14,
      type: "mp4",
      device: "ios",
      url: "public/assets/video/Bike720.mp4",
      note: "",
      title: "Video from Assets directory",
    },
    {
      id: 15,
      type: "mp4",
      device: "ios",
      url: "file:///var/mobile/Containers/Data/Application/22A433FD-D82D-4989-8BE6-9FC49DEA20BB/Documents/Bike720.mp4",
      note: "",
      title: "Video on App's Documents directory",
    },
    {
      id: 16,
      type: "mp4",
      device: "ios",
      url: "file:///var/mobile/Documents/Bike720.mp4",
      note: "",
      title: "Video on Documents directory",
    },
    {
      id: 17,
      type: "mp4",
      device: "ios",
      url: "file:///var/mobile/Downloads/Bike720.mp4",
      note: "",
      title: "Video on Download directory",
    },
    {
      id: 18,
      type: "mp4",
      device: "ios",
      url: "file:///var/mobile/Media/DCIM/100APPLE/Bike720.mp4",
      note: "",
      title: "Video on Media/DCIM directory",
    },
    {
      id: 19,
      type: "mp4",
      device: "all",
      url: "https://raw.githubusercontent.com/jepiqueau/jepiqueau.github.io/master/videos/Bike720.mp4",
      note: "Test Bike720",
      title: "Video from ",
    },

];

  constructor() {
    this.createDeviceVideo();
  }

  public getVideos(platform: string): Video[] {
    if (platform === 'web') {
      // Filter videos for Web platform (device: 'all')
      return this.videos.filter((video) => video.device === 'all');
    } else if (platform === 'android') {
      // Filter videos for Android platform (device: 'all' or 'android')
      return this.videos.filter((video) => video.device === 'all' || video.device === 'android');
    } else if (platform === 'ios') {
      // Filter videos for iOS platform (device: 'all' or 'ios')
      return this.videos.filter((video) => video.device === 'all' || video.device === 'ios');
    } else {
      return [];
    }
  }
  public getVideoById(id: number): Video | undefined {
    return this.videos.find(video => video.id === id);
  }
  private async createDeviceVideo(): Promise<void> {
    const platform = Capacitor.getPlatform();
    if (['ios', 'android'].includes(platform)) {
      // file Bike720.mp4
      const urlBike = "https://raw.githubusercontent.com/jepiqueau/jepiqueau.github.io/master/videos/Bike720.mp4";

//      const uri = await this.downloadVideotoDevice(urlBike);
      const uri = await this.fetchingVideoToDevice(urlBike, Directory.Documents);
      console.log(`###### uri : ${uri} ######`)
      if(uri !== undefined) {
        await this.copyVideoToOthersDirectories(uri,platform);
      }
      // test with a given path
      const vUri = await this.fetchingVideoToDevice(urlBike, Directory.Documents,"KSSV/documents/1/videos/2-12-2023-101222.mp4" );
      console.log(`###### vUri : ${vUri} ######`)

    }
  }
  public async fetchingVideoToDevice(url:string, directory: Directory, path: string = ""): Promise<string | undefined> {
    const urlName = path.length === 0 ? this.getFileName(url)! : path;
    const isVideoExists = await this.isFileExists(urlName, directory);
    console.log(`###### isVideoExists: ${isVideoExists} ######`)
    if(!isVideoExists) {
      let response = await fetch(url);
      console.log(" > fetched url");
      let dbBlob = await response.blob();
      let vBase64 = await this.getBlobAsBase64(dbBlob);
      console.log("  > converted url blob to base64\n");
      await Filesystem.writeFile({ data: vBase64, path: urlName, recursive: true, directory: directory });
      console.log( "  > saved vBase64 in Documents folder\n");
      return (await Filesystem.getUri({
        path: urlName,
        directory: Directory.Documents
      })).uri;
    } else {
      return (await Filesystem.getUri({
        path: urlName,
        directory: Directory.Documents
      })).uri;
    }

  }
  private getBlobAsBase64(blob: Blob): Promise<string> {
    return new Promise((resolve, _) => {
      let reader = new FileReader();
      reader.onload = (event: any) => {
        resolve(event.target.result);
      };
      reader.readAsDataURL(blob);
    });
  }

  private async copyVideoToOthersDirectories(uri: string,platform: string): Promise<void> {
    const uriName = this.getFileName(uri);
    let toPathDocum: string = "";
    let toPathDownl: string = "";
    let toPathDCIM: string = "";
    if (platform === 'ios') {
      const containersIndex = uri.indexOf('Containers');
      const folderPath = containersIndex !== -1 ? uri.substring(0, containersIndex) : uri;
      toPathDocum = `${folderPath}Documents/${uriName!}`;
      toPathDownl = `${folderPath}Downloads/${uriName!}`;
      toPathDCIM = `${folderPath}Media/DCIM/100APPLE/${uriName!}`;
    } else if (platform === 'android') {
      const parentPathIndex = uri.indexOf('Documents');
      const parentPath = parentPathIndex !== -1 ? uri.substring(0, parentPathIndex) : uri;
      toPathDownl = `${parentPath}Download/${uriName!}`;
      toPathDCIM = `${parentPath}DCIM/Camera/${uriName!}`;
    }
    console.log(`###### toPathDocum : ${toPathDocum} ######`)
    console.log(`###### toPathDownl : ${toPathDownl} ######`)
    console.log(`###### toPathDCIM : ${toPathDCIM} ######`)

    if(toPathDocum.length > 0 && !(await this.isFileExists(toPathDocum))) {
      const rc1 = await Filesystem.copy ({
        from: uri,
        to: toPathDocum
      });
      console.log(`&&&&rc1 : ${JSON.stringify(rc1)}`)
    }
    if(toPathDownl.length > 0 && !(await this.isFileExists(toPathDownl))) {
      const rc2 = await Filesystem.copy ({
        from: uri,
        to: toPathDownl
      });
      console.log(`&&&&rc2 : ${JSON.stringify(rc2)}`)
    }
    if(toPathDCIM.length > 0 && !(await this.isFileExists(toPathDCIM))) {
      const rc3 = await Filesystem.copy ({
        from: uri,
        to: toPathDCIM
      });
      console.log(`&&&&rc3 : ${JSON.stringify(rc3)}`)
    }
  }

  private async isFileExists(path: string, directory?: Directory): Promise<boolean> {

    try {
      const options: StatOptions = {} as StatOptions;
      options.path = path;
      const dir = directory ? directory : "";
      if (dir.length > 0) options.directory = directory;
      console.log(`###### isFileExists options: ${JSON.stringify(options)}  ######`)
      const info = await Filesystem.stat(options);
      console.log(`&&&&& info: ${JSON.stringify(info)} &&&&&`)
      return true;
    } catch (error) {
      return false;
    }
  }
  private getFileName(url: string) : string | undefined{
    const urlObject = new URL(url);
     return urlObject.pathname.split('/').pop();
  }
}

if you look at the id=13 you see the url = "file:///sdcard/Documents/KSSV/documents/1/videos/2-12-2023-101222.mp4"

now on the page where you have the call to videoPlayer initPlayer method you will have something like this

  private data = inject(DataService);
  private videoIndex: number = 13;
  private videoPlayer: any;
  private platform: string = 'web';
  private video: Video | undefined = {} as Video;

...
  async ngAfterViewInit() {
    // Get the selected video
    this.video = this.data.getVideoById(this.videoIndex);
    // Define the platform and the video player
    const info = await Device.getInfo();
    this.platform = info.platform;
    this.videoPlayer = CapacitorVideoPlayer;
    const props: any = {};
    if (this.video!.url != null) {
      props.mode = "fullscreen";
      props.url = this.video!.url;
      props.playerId = 'fullscreen';
      props.componentTag = 'YOUR_COMPONENT_TAG';
      if(this.video!.subtitle != null) props.subtitle = this.video!.subtitle;
      if(this.video!.stlang != null) props.stlang = this.video!.stlang;
      console.log('&&&& props: &&&&\n', JSON.stringify(props))
      const res: any = await this.videoPlayer.initPlayer(props);
    }
}

and this work well

jepiqueau commented 11 months ago

@paulisidore this will come soon in the update version of the tutorial Ionic7Angular-VideoPlayer-App

paulisidore commented 11 months ago

thank you very much for your support, @jepiqueau , i will come back to you after doing that.

jepiqueau commented 11 months ago

@paulisidore i have updated the tutorial

jepiqueau commented 9 months ago

@paulisidore Where do you stand now ? is it fixed? i close the issue not having news back from you. Fill free to reopen it if it is required

paulisidore commented 5 months ago

Hello @jepiqueau, sorry for the delay, I have been suffering and very busy taking care of myself. I come back to thank you again your example is perfect and everything works as expected. I implemented the code today with Capacitor 6 instead of 5 as in the project by forcing the installation of the Plugin.

Thank you and again take care of yourself and your family. Very Warmly

jepiqueau commented 5 months ago

@paulisidore thanks for your support. Life is not always easy. Good luck for your project