FileIO Wrapping

GPAC allows usage of wrappers for file operations (open, close, read, write, seek...), and such wrappers can be constructed from NodeJS.

A FileIO wrapper is constructed using:

- the URL you want to wrap
- a 'factory' object providing the callbacks for GPAC.
- an optional boolean indicating if direct memory should be used (default), or if array buffers are copied between GPAC and NodeJS.
 
Let's define a factory that simply calls NodeJS file system calls:

```
const filesys = require('fs');

let fio_factory = {
	open: function(url, mode) {

		this.file = null;
		this.size = 0;
		this.position = 0;
		this.read_mode = false;
		this.is_eof = false;
		this.url = url;
		//NodeJS does not accept 't' or 'b' indicators, always assumes binary
		mode = mode.replace('b', '');
		mode = mode.replace('t', '');

		try {
			this.file = filesys.openSync(url, mode);
		} catch (e) {
			console.log('Fail to open ' + url + ' in mode ' + mode + ': ' + e);
			return false;
		}
		//file is read or append, get the file size
		if (mode.indexOf('w')<0) {
			let stats = filesys.fstatSync(this.file);
			this.size = stats.size;
			if (mode.indexOf('a+')>=0) {
				this.position = this.size;
			}
			this.read_mode = true;
		}
		return true;
	},
	close: function() {
		filesys.closeSync(this.file);
	},
	read: function(buf) {
		let nb_bytes = 0;
		try {
			nb_bytes = filesys.readSync(this.file, buf, 0, buf.length, this.position);
		} catch (e) {
			console.log('read error: ' + e);
			return 0;
		}
		if (!nb_bytes) this.is_eof = true;
		this.position += nb_bytes;
		return nb_bytes;
	},
	write: function(buf) {
		let nb_bytes = filesys.writeSync(this.file, buf, 0, buf.length, this.position);
		if (this.position == this.size) {
			this.size += nb_bytes;
		}
		this.position += nb_bytes;
		return nb_bytes;
	},
	seek: function(pos, whence) {
		this.is_eof = false;
		if (pos<0) return -1;
		//seek set
		if (whence==0) {
			this.position = pos;
		}
		//seek cur
		else if (whence==1) {
			this.position += pos;
		}
		//seek end
		else if (whence==2) {
			if (this.size < pos) return -1;
			this.position = this.size - pos;
		} else {
			return -1;
		}
		return 0;
	},
	tell: function() {
		return this.position;
	},
	eof: function() {
		return this.is_eof;
	},
	exists: function(url) {
		try {
			filesys.accessSync(url);
		} catch (err) {
			return false;
		}
		return true;
	}
};
```

You can then wrap an input url using:

```
src_wrap = new gpac.FileIO("mysource.hvc", fio_factory);
dst_wrap = new gpac.FileIO("mydest.mp4", fio_factory);
f1 = fs.load_src(src_wrap.url);
f2 = fs.load_dst(dst_wrap.url+':option');

```

The wrapping can be useful when you don't want to do any IO with the produced content, or if your source content is not a file.

When opening a file, a new empty object is created and the 'open' callback is called with this new object as `this`.

This allows handling, with a single wrapper, cases where a URL resolves in multiple URLs when processing, for example DASH or HLS with manifest file(s) and media segments.

Note that all FileIO methods must be synchronous.

_NOTE When running the session in multi-thread mode, file IO callbacks are always executed by the main thread (NodeJS main)._