2 * An example showing how to read files using custom I/O routines. This
3 * specific example uses PhysFS to read files from zip, 7z, and some other
20 // Inherit from std::streambuf to handle custom I/O (PhysFS for this example)
21 class PhysFSBuf final
: public std::streambuf
{
22 alure::Array
<char_type
,4096> mBuffer
;
23 PHYSFS_File
*mFile
{nullptr};
25 int_type
underflow() override
27 if(mFile
&& gptr() == egptr())
29 // Read in the next chunk of data, and set the read pointers on
31 PHYSFS_sint64 got
= PHYSFS_read(mFile
,
32 mBuffer
.data(), sizeof(char_type
), mBuffer
.size()
34 if(got
!= -1) setg(mBuffer
.data(), mBuffer
.data(), mBuffer
.data()+got
);
37 return traits_type::eof();
38 return traits_type::to_int_type(*gptr());
41 pos_type
seekoff(off_type offset
, std::ios_base::seekdir whence
, std::ios_base::openmode mode
) override
43 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
44 return traits_type::eof();
46 // PhysFS only seeks using absolute offsets, so we have to convert cur-
47 // and end-relative offsets.
51 case std::ios_base::beg
:
54 case std::ios_base::cur
:
55 // Need to offset for the file offset being at egptr() while
56 // the requested offset is relative to gptr().
57 offset
-= off_type(egptr()-gptr());
58 if((fpos
=PHYSFS_tell(mFile
)) == -1)
59 return traits_type::eof();
60 // If the offset remains in the current buffer range, just
61 // update the pointer.
62 if(offset
< 0 && -offset
<= off_type(egptr()-eback()))
64 setg(eback(), egptr()+offset
, egptr());
70 case std::ios_base::end
:
71 if((fpos
=PHYSFS_fileLength(mFile
)) == -1)
72 return traits_type::eof();
77 return traits_type::eof();
80 if(offset
< 0) return traits_type::eof();
81 if(PHYSFS_seek(mFile
, offset
) == 0)
83 // HACK: Workaround a bug in PhysFS. Certain archive types error
84 // when trying to seek to the end of the file. So if seeking to the
85 // end of the file fails, instead try seeking to the last byte and
87 if(offset
!= PHYSFS_fileLength(mFile
))
88 return traits_type::eof();
89 if(PHYSFS_seek(mFile
, offset
-1) == 0)
90 return traits_type::eof();
91 PHYSFS_read(mFile
, mBuffer
.data(), 1, 1);
93 // Clear read pointers so underflow() gets called on the next read
95 setg(nullptr, nullptr, nullptr);
99 pos_type
seekpos(pos_type pos
, std::ios_base::openmode mode
) override
101 // Simplified version of seekoff
102 if(!mFile
|| (mode
&std::ios_base::out
) || !(mode
&std::ios_base::in
))
103 return traits_type::eof();
105 if(PHYSFS_seek(mFile
, pos
) == 0)
106 return traits_type::eof();
107 setg(nullptr, nullptr, nullptr);
112 bool open(const char *filename
) noexcept
114 mFile
= PHYSFS_openRead(filename
);
115 if(!mFile
) return false;
119 PhysFSBuf() = default;
120 ~PhysFSBuf() override
127 // Inherit from std::istream to use our custom streambuf
128 class Stream final
: public std::istream
{
129 PhysFSBuf mStreamBuf
;
132 Stream(const char *filename
) : std::istream(nullptr)
136 // Set the failbit if the file failed to open.
137 if(!mStreamBuf
.open(filename
)) clear(failbit
);
141 // Inherit from alure::FileIOFactory to use our custom istream
142 class FileFactory final
: public alure::FileIOFactory
{
144 FileFactory(const char *argv0
)
146 // Need to initialize PhysFS before using it
147 if(PHYSFS_init(argv0
) == 0)
148 throw std::runtime_error(alure::String("Failed to initialize PhysFS: ") +
149 PHYSFS_getLastError());
151 std::cout
<< "Initialized PhysFS, supported archive formats:";
152 for(const PHYSFS_ArchiveInfo
**i
= PHYSFS_supportedArchiveTypes();*i
!= NULL
;i
++)
153 std::cout
<< "\n "<<(*i
)->extension
<<": "<<(*i
)->description
;
154 std::cout
<<std::endl
;
156 ~FileFactory() override
161 alure::UniquePtr
<std::istream
> openFile(const alure::String
&name
) noexcept override
163 auto stream
= alure::MakeUnique
<Stream
>(name
.c_str());
164 if(stream
->fail()) stream
= nullptr;
165 return std::move(stream
);
168 // A PhysFS-specific function to mount a new path to the virtual directory
170 static bool Mount(const char *path
, const char *mountPoint
=nullptr, int append
=0)
172 std::cout
<< "Adding new file source "<<path
;
173 if(mountPoint
) std::cout
<< " to "<<mountPoint
;
174 std::cout
<<"..."<<std::endl
;
176 if(PHYSFS_mount(path
, mountPoint
, append
) == 0)
178 std::cerr
<< "Failed to add "<<path
<<": "<<PHYSFS_getLastError() <<std::endl
;
184 static void ListDirectory(std::string
&& dir
)
186 char **files
= PHYSFS_enumerateFiles(dir
.c_str());
187 for(int i
= 0;files
[i
];i
++)
189 std::string file
= dir
+ files
[i
];
190 if(PHYSFS_isDirectory(file
.c_str()))
191 ListDirectory(file
+"/");
193 std::cout
<<" "<<file
<<"\n";
195 PHYSFS_freeList(files
);
200 // Helper class+method to print the time with human-readable formatting.
202 alure::Seconds mTime
;
204 inline std::ostream
&operator<<(std::ostream
&os
, const PrettyTime
&rhs
)
206 using hours
= std::chrono::hours
;
207 using minutes
= std::chrono::minutes
;
208 using seconds
= std::chrono::seconds
;
209 using centiseconds
= std::chrono::duration
<int64_t, std::ratio
<1, 100>>;
210 using std::chrono::duration_cast
;
212 centiseconds t
= duration_cast
<centiseconds
>(rhs
.mTime
);
219 // Only handle up to hour formatting
221 os
<< duration_cast
<hours
>(t
).count() << 'h' << std::setfill('0') << std::setw(2)
222 << duration_cast
<minutes
>(t
).count() << 'm';
224 os
<< duration_cast
<minutes
>(t
).count() << 'm' << std::setfill('0');
225 os
<< std::setw(2) << (duration_cast
<seconds
>(t
).count() % 60) << '.' << std::setw(2)
226 << (t
.count() % 100) << 's' << std::setw(0) << std::setfill(' ');
233 int main(int argc
, char *argv
[])
235 alure::ArrayView
<const char*> args(argv
, argc
);
239 std::cerr
<< "Usage: "<<args
.size()<<" [-device <device name>] "
240 "-add <directory | archive> file_entries ..." <<std::endl
;
244 // Set our custom factory for file IO. From now on, all filenames given to
245 // Alure will be used with our custom factory.
246 alure::FileIOFactory::set(alure::MakeUnique
<FileFactory
>(args
.front()));
247 args
= args
.slice(1);
249 alure::DeviceManager devMgr
= alure::DeviceManager::getInstance();
252 if(args
.size() > 2 && args
[0] == alure::StringView("-device"))
254 dev
= devMgr
.openPlayback(args
[1], std::nothrow
);
255 if(!dev
) std::cerr
<< "Failed to open \""<<args
[1]<<"\" - trying default" <<std::endl
;
256 args
= args
.slice(2);
258 if(!dev
) dev
= devMgr
.openPlayback();
259 std::cout
<< "Opened \""<<dev
.getName()<<"\"" <<std::endl
;
261 alure::Context ctx
= dev
.createContext();
262 alure::Context::MakeCurrent(ctx
);
264 for(;!args
.empty();args
= args
.slice(1))
266 if(args
.size() > 1 && alure::StringView("-add") == args
[0])
268 args
= args
.slice(1);
269 FileFactory::Mount(args
.front());
270 std::cout
<<"Available files:\n";
271 FileFactory::ListDirectory("/");
276 alure::SharedPtr
<alure::Decoder
> decoder
= ctx
.createDecoder(args
.front());
277 alure::Source source
= ctx
.createSource();
278 source
.play(decoder
, 12000, 4);
279 std::cout
<< "Playing "<<args
.front()<<" ("
280 << alure::GetSampleTypeName(decoder
->getSampleType())<<", "
281 << alure::GetChannelConfigName(decoder
->getChannelConfig())<<", "
282 << decoder
->getFrequency()<<"hz)" <<std::endl
;
284 double invfreq
= 1.0 / decoder
->getFrequency();
285 while(source
.isPlaying())
287 std::cout
<< "\r "<<PrettyTime
{source
.getSecOffset()}<<" / "<<
288 PrettyTime
{alure::Seconds(decoder
->getLength()*invfreq
)};
290 std::this_thread::sleep_for(std::chrono::milliseconds(25));
293 std::cout
<<std::endl
;
299 alure::Context::MakeCurrent(nullptr);