1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
|
<html>
<head>
<title>The VDR Plugin System</title>
</head>
<body bgcolor="white">
<center><h1>The VDR Plugin System</h1></center>
<center><b>Version 1.2</b></center>
<p>
<center>
Copyright © 2003 Klaus Schmidinger<br>
<a href="mailto:kls@cadsoft.de">kls@cadsoft.de</a><br>
<a href="http://www.cadsoft.de/vdr">www.cadsoft.de/vdr</a>
</center>
<p>
VDR provides an easy to use plugin interface that allows additional functionality
to be added to the program by implementing a dynamically loadable library file.
This interface allows programmers to develop additional functionality for VDR completely
separate from the core VDR source, without the need of patching the original
VDR code (and all the problems of correlating various patches).
<p>
This document is divided into two parts, the first one describing the
<a href="#Part I - The External Interface"><i>external</i> interface</a>
of the plugin system, and the second one describing the
<a href="#Part II - The Internal Interface"><i>internal</i> interface</a>.
The <i>external</i> interface handles everything necessary for a plugin to get hooked into the core
VDR program and present itself to the user.
The <i>internal</i> interface provides the plugin code access to VDR's internal data
structures and allows it to hook itself into specific areas to perform special actions.
<hr>
<h1>Table Of Contents</h1>
<ul>
<li><a href="#Part I - The External Interface">Part I - The External Interface</a>
<ul>
<li><a href="#Quick start">Quick start</a>
<li><a href="#The name of the plugin">The name of the plugin</a>
<li><a href="#The plugin directory structure">The plugin directory structure</a>
<li><a href="#Initializing a new plugin directory">Initializing a new plugin directory</a>
<li><a href="#The actual implementation">The actual implementation</a>
<li><a href="#Construction and Destruction">Construction and Destruction</a>
<li><a href="#Version number">Version number</a>
<li><a href="#Description">Description</a>
<li><a href="#Command line arguments">Command line arguments</a>
<li><a href="#Command line help">Command line help</a>
<li><a href="#Getting started">Getting started</a>
<li><a href="#Main menu entry">Main menu entry</a>
<li><a href="#User interaction">User interaction</a>
<li><a href="#Housekeeping">Housekeeping</a>
<li><a href="#Setup parameters">Setup parameters</a>
<li><a href="#The Setup menu">The Setup menu</a>
<li><a href="#Configuration files">Configuration files</a>
<li><a href="#Internationalization">Internationalization</a>
<li><a href="#Loading plugins into VDR">Loading plugins into VDR</a>
<li><a href="#Building the distribution package">Building the distribution package</a>
</ul>
<li><a href="#Part II - The Internal Interface">Part II - The Internal Interface</a>
<ul>
<li><a href="#Status monitor">Status monitor</a>
<li><a href="#Players">Players</a>
<li><a href="#Receivers">Receivers</a>
<li><a href="#The On Screen Display">The On Screen Display</a>
<li><a href="#Devices">Devices</a>
<li><a href="#Dolby Digital">Dolby Digital</a>
<li><a href="#Remote Control">Remote Control</a>
</ul>
</ul>
<a name="Part I - The External Interface"><hr><center><h1>Part I - The External Interface</h1></center>
<a name="Quick start"><hr><h2>Quick start</h2>
<center><i><b>Can't wait, can't wait!</b></i></center><p>
Actually you should read this entire document before starting to work with VDR plugins,
but you probably want to see something happening right away <tt>;-)</tt>
<p>
So, for a quick demonstration of the plugin system, there is a sample plugin called
"hello" that comes with the VDR source. To test drive this one, do the following:
<ul>
<li>change into the VDR source directory
<li><b><tt>make</tt></b> the VDR program with your usual <tt>REMOTE=...</tt> (and maybe other) options
<li>do <b><tt>make plugins</tt></b> to build the plugin
<li>run VDR with <b><tt>vdr -V</tt></b> to see the version information
<li>run VDR with <b><tt>vdr -h</tt></b> to see the command line options
<li>run VDR with <b><tt>vdr -Phello</tt></b>
<li>open VDR's main menu and select the <i>Hello</i> item
<li>open the <i>Setup</i> menu from VDR's main menu and select <i>Plugins</i>
</ul>
If you enjoyed this brief glimpse into VDR plugin handling, read through the rest of
this document and eventually write your own VDR plugin.
<a name="The name of the plugin"><hr><h2>The name of the plugin</h2>
<center><i><b>Give me some I.D.!</b></i></center><p>
One of the first things to consider when writing a VDR plugin is giving the thing
a proper name. This name will be used in the VDR command line in order to load
the plugin, and will also be the name of the plugin's source directory, as well
as part of the final library name.
<p>
The plugin's name should typically be as short as possible. Three letter
abbreviations like <b><tt>dvd</tt></b> (for a DVD player) or <b><tt>mp3</tt></b>
(for an MP3 player) would be good choices. It is also recommended that the name
consists of only lowercase letters and digits.
No other characters should be used here.
<p>
A plugin can access its name through the (non virtual) member function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
const char *Name(void);
</pre></td></tr></table><p>
The actual name is derived from the plugin's library file name, as defined in the
next chapter.
<a name="The plugin directory structure"><hr><h2>The plugin directory structure</h2>
<center><i><b>Where is everybody?</b></i></center><p>
By default plugins are located in a directory named <tt>PLUGINS</tt> below the
VDR source directory. Inside this directory the following subdirectory structure
is used:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
VDR/PLUGINS/src
VDR/PLUGINS/src/hello
VDR/PLUGINS/lib
VDR/PLUGINS/lib/libvdr-hello.so.1.1.0
</pre></td></tr></table><p>
The <tt>src</tt> directory contains one subdirectory for each plugin, which carries
the name of that plugin (in the above example that would be <tt>hello</tt>).
What's inside the individual source directory of a
plugin is entirely up to the author of that plugin. The only prerequisites are
that there is a <tt>Makefile</tt> that provides the targets <tt>all</tt> and
<tt>clean</tt>, and that a call to <tt>make all</tt> actually produces a dynamically
loadable library file for that plugin (we'll get to the details later).
<p>
The <tt>lib</tt> directory contains the dynamically loadable libraries of all
available plugins. Note that the names of these files are created by concatenating
<p>
<table border=2>
<tr><td align=center><b><tt>libvdr-</tt></b></td><td align=center><b><tt>hello</tt></b></td><td align=center><b><tt>.so.</tt></b></td><td align=center><b><tt>1.1.0</tt></b></td></tr>
<tr><td align=center><font size=-1>VDR plugin<br>library prefix</font></td><td align=center><font size=-1>name of<br>the plugin</font></td><td align=center><font size=-1>shared object<br>indicator</font></td><td align=center><font size=-1>VDR version number<br>this plugin was<br>compiled for</font></td></tr>
</table>
<p>
The plugin library files can be stored in any directory. If the default organization
is not used, the path to the plugin directory has be be given to VDR through the
<b><tt>-L</tt></b> option.
<p>
The VDR <tt>Makefile</tt> contains the target <tt>plugins</tt>, which calls
<tt>make all</tt> in every directory found under <tt>VDR/PLUGINS/src</tt>,
plus the target <tt>plugins-clean</tt>, which calls <tt>make clean</tt> in
each of these directories.
<p>
If you download a plugin <a href="#Building the distribution package">package</a>
from the web, it will typically have a name like
<p>
<tt>vdr-hello-0.0.1.tgz</tt>
<p>
and will unpack into a directory named
<p>
<tt>hello-0.0.1</tt>
<p>
To use the <tt>plugins</tt> and <tt>plugins-clean</tt> targets from the VDR <tt>Makefile</tt>
you need to unpack such an archive into the <tt>VDR/PLUGINS/src</tt> directory and
create a symbolic link with the basic plugin name, as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
ln -s hello-0.0.1 hello
</pre></td></tr></table><p>
Since the VDR <tt>Makefile</tt> only searches for directories with names consisting
of only lowercase characters and digits, it will only follow the symbolic links, which
should lead to the current version of the plugin you want to use. This way you can
have several different versions of a plugin source (like <tt>hello-0.0.1</tt> and
<tt>hello-0.0.2</tt>) and define which one to actually use through the symbolic link.
<a name="Initializing a new plugin directory"><hr><h2>Initializing a new plugin directory</h2>
<center><i><b>A room with a view</b></i></center><p>
Call the Perl script <tt>newplugin</tt> from the VDR source directory to create
a new plugin directory with a <tt>Makefile</tt> and a main source file implementing
the basic derived plugin class.
You will also find a <tt>README</tt> file there with some inital text, where you
should fill in actual information about your project.
A <tt>HISTORY</tt> file is set up with an "Initial revision" entry. As your project
evolves, you should add the changes here with date and version number.
<p>
<tt>newplugin</tt> also creates a copy of the GPL license file <tt>COPYING</tt>,
assuming that you will release your work under that license. Change this if you
have other plans.
<p>
Add further files and maybe subdirectories to your plugin source directory as
necessary. Don't forget to adapt the <tt>Makefile</tt> appropriately.
<a name="The actual implementation"><hr><h2>The actual implementation</h2>
<center><i><b>Use the source, Luke!</b></i></center><p>
A newly initialized plugin doesn't really do very much yet.
If you <a href="#Loading plugins into VDR">load it into VDR</a> you will find a new
entry in the main menu, with the same name as your plugin (where the first character
has been converted to uppercase). There will also be a new entry named "Plugins" in
the "Setup" menu, which will bring up a list of all loaded plugins, through which you
can access each plugin's own setup parameters (if it provides any).
<p>
To implement actual functionality into your plugin you need to edit the source file
that was generated as <tt>PLUGINS/src/name.c</tt>. Read the comments in that file
to see where you can bring in your own code. The following sections of this document
will walk you through the individual member functions of the plugin class.
<p>
Depending on what your plugin shall do, you may or may not need all of the given
member functions. Except for the <tt>MainMenuEntry()</tt> function they all by default
return values that will result in no actual functionality. You can either completely
delete unused functions from your source file, or just leave them as they are.
If your plugin shall not be accessible through VDR's main menu, simply remove
(or comment out) the line implementing the <tt>MainMenuEntry()</tt> function.
<p>
At the end of the plugin's source file you will find a line that looks like this:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
VDRPLUGINCREATOR(cPluginHello);
</pre></td></tr></table><p>
This is the "magic" hook that allows VDR to actually load the plugin into
its memory. You don't need to worry about the details behind all this.
<p>
If your plugin requires additional source files, simply add them to your plugin's
source directory and adjust the <tt>Makefile</tt> accordingly.
<p>
Header files usually contain preprocessor statements that prevent the same
file (or rather its contents, to be precise) from being included more than once, like
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#ifndef __I18N_H
#define __I18N_H
...
#endif //__I18N_H
</pre></td></tr></table><p>
The example shown here is the way VDR does this in its core source files.
It takes the header file's name, converts it to all uppercase, replaces the
dot with an underline and preceedes the whole thing with two underlines.
The GNU library header files do this pretty much the same way, except that they
usually precede the name with only one underline (there are exceptions, though).
<p>
As long as you make shure that none of your plugin's header files will be named
like one of VDR's header files, you can use the same method as VDR. However,
if you want to name a header file like one that is already existing in VDR's
source (<tt>i18n.h</tt> would be a possible candidate for this), you may want
to make sure that the macros used here don't clash. How you do this is completely
up to you. You could, for instance, prepend the macro with a <tt>'P'</tt>, as in
<tt>P__I18N_H</tt>, or leave out the trailing <tt>_H</tt>, as in <tt>__I18N</tt>,
or use a completely different way to make sure a header file is included only once.
<p>
The 'hello' example that comes with VDR makes use of <a href="#Internationalization">internationalization</a>
and implements a file named <tt>i18n.h</tt>. To make sure it won't clash with VDR's
<tt>i18n.h</tt> it uses the macro <tt>_I18N__H</tt> (one underline at the beginning
and two replacing the dot).
<a name="Construction and Destruction"><hr><h2>Construction and Destruction</h2>
<center><i><b>What goes up, must come down...</b></i></center><p>
The constructor and destructor of a plugin are defined as
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
cPlugin(void);
virtual ~cPlugin();
</pre></td></tr></table><p>
The <b>constructor</b> shall initialize any member variables the plugin defines, but
<b>must not access any global structures of VDR</b>.
It also must not create any threads or other large data structures. These things
are done in the
<a href="#Getting started"><tt>Initialize()</tt></a> or
<a href="#Getting started"><tt>Start()</tt></a>
function later.
Constructing a plugin object shall not have any side effects or produce any output,
since VDR, for instance, has to create the plugin objects in order to get their
command line help - and after that immediately destroys them again.
<p>
The <b>destructor</b> has to clean up any data created by the plugin, and has to
take care that any threads the plugin may have created will be stopped.
<p>
Of course, if your plugin doesn't define any member variables that need to be
initialized (and deleted), you don't need to implement either of these functions.
<a name="Version number"><hr><h2>Version number</h2>
<center><i><b>Which incarnation is this?</b></i></center><p>
Every plugin must have a version number of its own, which does not necessarily
have to be in any way related to the VDR version number.
VDR requests a plugin's version number through a call to the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual const char *Version(void) = 0;
</pre></td></tr></table><p>
Since this is a "pure" virtual function, any derived plugin class <b>must</b>
implement it. The returned string should identify this version of the plugin.
Typically this would be something like "0.0.1", but it may also contain other
information, like for instance "0.0.1pre2" or the like. The string should only
be as long as really necessary, and shall not contain the plugin's name itself.
Here's an example:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
static const char *VERSION = "0.0.1";
const char *cPluginHello::Version(void)
{
return VERSION;
}
</pre></td></tr></table><p>
Note that the definition of the version number is expected to be located in the
main source file, and must be written as
<pre>
static const char *VERSION = ...
</pre>
just like shown in the above example. This is a convention that allows the <tt>Makefile</tt>
to extract the version number when generating the file name for the distribution archive.
<p>
A new plugin project should start with version number <tt>0.0.1</tt> and should reach
version <tt>1.0.0</tt> once it is completely operative and well tested. Following the
Linux kernel version numbering scheme, versions with <i>even</i> release numbers
(like <tt>1.0.x</tt>, <tt>1.2.x</tt>, <tt>1.4.x</tt>...) should be stable releases,
while those with <i>odd</i> release numbers (like <tt>1.1.x</tt>, <tt>1.3.x</tt>,
<tt>1.5.x</tt>...) are usually considered "under development". The three parts of
a version number are not limited to single digits, so a version number of <tt>1.2.15</tt>
would be acceptable.
<a name="Description"><hr><h2>Description</h2>
<center><i><b>What is it that you do?</b></i></center><p>
In order to tell the user what exactly a plugin does, it must implement the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual const char *Description(void) = 0;
</pre></td></tr></table><p>
which returns a short, one line description of the plugin's purpose:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
static const char *DESCRIPTION = "A friendly greeting";
virtual const char *Description(void)
{
return tr(DESCRIPTION);
}
</pre></td></tr></table><p>
Note the <tt>tr()</tt> around the <tt>DESCRIPTION</tt>, which allows the description
to be <a href="#Internationalization">internationalized</a>.
<a name="Command line arguments"><hr><h2>Command line arguments</h2>
<center><i><b>Taking orders</b></i></center><p>
A VDR plugin can have command line arguments just like any normal program.
If a plugin wants to react on command line arguments, it needs to implement
the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool ProcessArgs(int argc, char *argv[]);
</pre></td></tr></table><p>
The parameters <tt>argc</tt> and <tt>argv</tt> have exactly the same meaning
as in a normal C program's <tt>main()</tt> function.
<tt>argv[0]</tt> contains the name of the plugin (as given in the <b><tt>-P</tt></b>
option of the <tt>vdr</tt> call).
<p>
Each plugin has its own set of command line options, which are totally independent
from those of any other plugin or VDR itself.
<p>
You can use the <tt>getopt()</tt> or <tt>getopt_long()</tt> function to process
these arguments. As with any normal C program, the strings pointed to by <tt>argv</tt>
will survive the entire lifetime of the plugin, so it is safe to store pointers to
these values inside the plugin. Here's an example:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
bool cPluginHello::ProcessArgs(int argc, char *argv[])
{
// Implement command line argument processing here if applicable.
static struct option long_options[] = {
{ "aaa", required_argument, NULL, 'a' },
{ "bbb", no_argument, NULL, 'b' },
{ NULL }
};
int c;
while ((c = getopt_long(argc, argv, "a:b", long_options, NULL)) != -1) {
switch (c) {
case 'a': option_a = optarg;
break;
case 'b': option_b = true;
break;
default: return false;
}
}
return true;
}
</pre></td></tr></table><p>
The return value must be <i>true</i> if all options have been processed
correctly, or <i>false</i> in case of an error. The first plugin that returns
<i>false</i> from a call to its <tt>ProcessArgs()</tt> function will cause VDR
to exit.
<a name="Command line help"><hr><h2>Command line help</h2>
<center><i><b>Tell me about it...</b></i></center><p>
If a plugin accepts command line options, it should implement the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual const char *CommandLineHelp(void);
</pre></td></tr></table><p>
which will be called if the user enters the <b><tt>-h</tt></b> option when starting VDR.
The returned string should contain the command line help for this plugin, formatted
in the same way as done by VDR itself:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
const char *cPluginHello::CommandLineHelp(void)
{
// Return a string that describes all known command line options.
return " -a ABC, --aaa=ABC do something nice with ABC\n"
" -b, --bbb activate 'plan B'\n";
}
</pre></td></tr></table><p>
This command line help will be printed directly below VDR's help texts (separated
by a line indicating the plugin's name, version and description), so if you use the
same formatting as shown here it will line up nicely.
Note that all lines should be terminated with a newline character, and should
be shorter than 80 characters.
<a name="Getting started"><hr><h2>Getting started</h2>
<center><i><b>Let's get ready to rumble!</b></i></center><p>
If a plugin implements a function that runs in the background (presumably in a
thread of its own), or wants to make use of <a href="#Internationalization">internationalization</a>,
it needs to implement one of the functions
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool Initialize(void);
virtual bool Start(void);
</pre></td></tr></table><p>
which are called once for each plugin at program startup.
The difference between these two functions is that <tt>Initialize()</tt> is
called early at program startup, while <tt>Start()</tt> is called after the primary
device and user interface has been set up, but before the main program loop is entered.
Inside the <tt>Start()</tt> function of any plugin it is guaranteed that the <tt>Initialize()</tt>
functions of all plugins have already been called. For many plugins it probably
doesn't matter which of these functions they implement, but it may be of importance
for, e.g., plugins that implement devices. Such plugins should create their cDevice
derived objects in <tt>Initialize()</tt>, so that other plugins can use them in their
<tt>Start()</tt> functions.
<p>
Inside this function the plugin must set up everything necessary to perform
its task. This may, for instance, be a thread that collects data from the DVB
stream, which is later presented to the user via a function that is available
from the main menu.
<p>
A return value of <i>false</i> indicates that something has gone wrong and the
plugin will not be able to perform its task. In that case, the plugin should
write a proper error message to the log file. The first plugin that returns
<i>false</i> from its <tt>Initialize()</tt> or <tt>Start()</tt> function will cause
VDR to exit.
<p>
If the plugin doesn't implement any background functionality or internationalized
texts, it doesn't need to implement either of these functions.
<a name="Main menu entry"><hr><h2>Main menu entry</h2>
<center><i><b>Today's special is...</b></i></center><p>
If the plugin implements a feature that the user shall be able to access
from VDR's main menu, it needs to implement the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual const char *MainMenuEntry(void);
</pre></td></tr></table><p>
The default implementation returns a <tt>NULL</tt> pointer, which means that
this plugin will not have an item in the main menu. Here's an example of a
plugin that will have a main menu item:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
static const char *MAINMENUENTRY = "Hello";
const char *cPluginHello::MainMenuEntry(void)
{
return tr(MAINMENUENTRY);
}
</pre></td></tr></table><p>
The menu entries of all plugins will be inserted into VDR's main menu right
after the <i>Recordings</i> item, in the same sequence as they were given
in the call to VDR.
<a name="User interaction"><hr><h2>User interaction</h2>
<center><i><b>It's showtime!</b></i></center><p>
If the user selects the main menu entry of a plugin, VDR calls the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual cOsdObject *MainMenuAction(void);
</pre></td></tr></table><p>
which can do one of three things:
<ul>
<li>Return a pointer to a <tt>cOsdMenu</tt> object which will be displayed
as a submenu of the main menu (just like the <i>Recordings</i> menu, for instance).
That menu can then implement further functionality and, for instance, could
eventually start a custom player to replay a file other than a VDR recording.
<li>Return a pointer to a <tt>cOsdObject</tt> object which will be displayed
instead of the normal menu. The derived <tt>cOsdObject</tt> can open a
<a href="#The On Screen Display">raw OSD</a> from within its <tt>Show()</tt>
function (it should not attempt to do so from within its constructor, since
at that time the OSD is still in use by the main menu).
See the 'osddemo' example that comes with VDR for a demonstration of how this
is done.
<li>Perform a specific action and return <tt>NULL</tt>. In that case the main menu
will be closed after calling <tt>MainMenuAction()</tt>.
</ul>
<b>
It is very important that a call to <tt>MainMenuAction()</tt> returns as soon
as possible! As long as the program stays inside this function, no other user
interaction is possible. If a specific action takes longer than a few seconds,
the plugin should launch a separate thread to do this.
</b>
<a name="Housekeeping"><hr><h2>Housekeeping</h2>
<center><i><b>Chores, chores...</b></i></center><p>
From time to time a plugin may want to do some regular tasks, like cleaning
up some files or other things. In order to do this it can implement the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual void Housekeeping(void);
</pre></td></tr></table><p>
which gets called when VDR is otherwise idle. The intervals between subsequent
calls to this function are not defined. There may be several hours between two
calls (if, for instance, there are recordings or replays going on) or they may
be as close as ten seconds. The only thing that is guaranteed is that there are
at least ten seconds between two subsequent calls to the <tt>Housekeeping()</tt>
function of the same plugin.
<p>
<b>
It is very important that a call to <tt>Housekeeping()</tt> returns as soon
as possible! As long as the program stays inside this function, no other user
interaction is possible. If a specific action takes longer than a few seconds,
the plugin should launch a separate thread to do this.
</b>
<a name="Setup parameters"><hr><h2>Setup parameters</h2>
<center><i><b>Remember me...</b></i></center><p>
If a plugin requires its own setup parameters, it needs to implement the following
functions to handle these parameters:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual cMenuSetupPage *SetupMenu(void);
virtual bool SetupParse(const char *Name, const char *Value);
</pre></td></tr></table><p>
The <tt>SetupMenu()</tt> function shall return the plugin's <a href="#The Setup menu"><i>Setup</i> menu</a>
page, where the user can adjust all the parameters known to this plugin.
<p>
<tt>SetupParse()</tt> will be called for each parameter the plugin has
previously stored in the global setup data (see below). It shall return
<i>true</i> if the parameter was parsed correctly, <i>false</i> in case of
an error. If <i>false</i> is returned, an error message will be written to
the log file (and program execution will continue).
A possible implementation of <tt>SetupParse()</tt> could look like this:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
bool cPluginHello::SetupParse(const char *Name, const char *Value)
{
// Parse your own setup parameters and store their values.
if (!strcasecmp(Name, "GreetingTime")) GreetingTime = atoi(Value);
else if (!strcasecmp(Name, "UseAlternateGreeting")) UseAlternateGreeting = atoi(Value);
else
return false;
return true;
</pre></td></tr></table><p>
It is important to make sure that the parameter names are exactly the same as
used in the <a href="#The Setup menu"><i>Setup</i> menu</a>'s <tt>Store()</tt> function.
<p>
The plugin's setup parameters are stored in the same file as VDR's parameters.
In order to allow each plugin (and VDR itself) to have its own set of parameters,
the <tt>Name</tt> of each parameter will be preceeded with the plugin's
name, as in
<p>
<tt>hello.GreetingTime = 3</tt>
<p>
The prefix will be handled by the core VDR setup code, so the individual
plugins need not worry about this.
<p>
To store its values in the global setup, a plugin has to call the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
void SetupStore(const char *Name, <i>type</i> Value);
</pre></td></tr></table><p>
where <tt>Name</tt> is the name of the parameter (<tt>"GreetingTime"</tt> in the above
example, without the prefix <tt>"hello."</tt>) and <tt>Value</tt> is a simple data type (like
<tt>char *</tt>, <tt>int</tt> etc).
Note that this is not a function that the individual plugin class needs to implement!
<tt>SetupStore()</tt> is a non-virtual member function of the <tt>cPlugin</tt> class.
<p>
To remove a parameter from the setup data, call <tt>SetupStore()</tt> with the appropriate
name and without any value, as in
<p>
<tt>SetupStore("GreetingTime");</tt>
<p>
The VDR menu "Setup/Plugins" will list all loaded plugins with their name,
version number and description. Selecting an item in this list will bring up
the plugin's "Setup" menu if that plugin has implemented the <tt>SetupMenu()</tt>
function.
<p>
Finally, a plugin doesn't have to implement the <tt>SetupMenu()</tt> if it only
needs setup parameters that are not directly user adjustable. It can use
<tt>SetupStore()</tt> and <tt>SetupParse()</tt> without presenting these
parameters to the user.
<a name="The Setup menu"><hr><h2>The Setup menu</h2>
<center><i><b>Have it your way!</b></i></center><p>
To implement a <i>Setup</i> menu, a plugin needs to derive a class from
<tt>cMenuSetupPage</tt> and implement its constructor and the pure virtual
<tt>Store()</tt> member function:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
int GreetingTime = 3;
int UseAlternateGreeting = false;
class cMenuSetupHello : public cMenuSetupPage {
private:
int newGreetingTime;
int newUseAlternateGreeting;
protected:
virtual void Store(void);
public:
cMenuSetupHello(void);
};
cMenuSetupHello::cMenuSetupHello(void)
{
newGreetingTime = GreetingTime;
newUseAlternateGreeting = UseAlternateGreeting;
Add(new cMenuEditIntItem( tr("Greeting time (s)"), &newGreetingTime));
Add(new cMenuEditBoolItem(tr("Use alternate greeting"), &newUseAlternateGreeting));
}
void cMenuSetupHello::Store(void)
{
SetupStore("GreetingTime", GreetingTime = newGreetingTime);
SetupStore("UseAlternateGreeting", UseAlternateGreeting = newUseAlternateGreeting);
}
</pre></td></tr></table><p>
In this example we have two global setup parameters (<tt>GreetingTime</tt> and <tt>UseAlternateGreeting</tt>).
The constructor initializes two private members with the values of these parameters, so
that the <i>Setup</i> menu can work with temporary copies (in order to discard any changes
if the user doesn't confirm them by pressing the "Ok" button).
After this the constructor adds the appropriate menu items, using internationalized texts
and the addresses of the temporary variables. That's all there is to inizialize a <i>Setup</i>
menu - the rest will be done by the core VDR code.
<p>
Once the user has pressed the "Ok" button to confirm the changes, the <tt>Store()</tt> function will
be called, in which all setup parameters must be actually stored in VDR's global setup data.
This is done by calling the <tt>SetupStore()</tt> function for each of the parameters.
The <i>Name</i> string given here will be used to identify the parameter in VDR's
<tt>setup.conf</tt> file, and will be automatically prepended with the plugin's name.
<p>
Note that in this small example the new values of the parameters are copied into the
global variables within each <tt>SetupStore()</tt> call. This is not mandatory, however.
You can first assign the temporary values to the global variables and then do the
<tt>SetupStore()</tt> calls, or you can define a class or struct that contains all
your setup parameters and use that one to copy all parameters with one single statement
(like VDR does with its cSetup class).
<a name="Configuration files"><hr><h2>Configuration files</h2>
<center><i><b>I want my own stuff!</b></i></center><p>
There may be situations where a plugin requires configuration files of its own, maybe
for data that can't be stored in the simple <a href="#Setup parameters">setup parameters</a>
of VDR, or maybe because it needs to launch other programs that simply need a separate
configuration file. While the plugin is free to store such files anywhere it
sees fit, it might be a good idea to put them in a common place, preferably
where other configuration data already exists. VDR provides the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
const char *ConfigDirectory(const char *PluginName = NULL);
</pre></td></tr></table><p>
which returns a string containing the directory that VDR uses for its own configuration
files (defined through the <tt><b>-c</b></tt> option in the call to VDR), extended by
<tt>"/plugins"</tt>. So assuming the VDR configuration directory is <tt>/video</tt>
(the default if no <tt><b>-c</b></tt> or <tt><b>-v</b></tt> option is given),
a call to <tt>ConfigDirectory()</tt> will return <tt>/video/plugins</tt>. The first
call to <tt>ConfigDirectory()</tt> will automatically make sure that the <tt>plugins</tt>
subdirectory will exist. If, for some reason, this cannot be achieved, <tt>NULL</tt>
will be returned.
<p>
The additional <tt>plugins</tt> directory is used to keep files from plugins apart
from those of VDR itself, making sure there will be no name clashes. If a plugin
needs only one extra configuration file, it is suggested that this file be named
<tt>name.conf</tt>, where <i>name</i> shall be the name of the plugin.
<p>
If a plugin needs more than one such file, it is suggested that the plugin stores
these in a subdirectory of its own, named after the plugin. To easily get such a name
the <tt>ConfigDirectory()</tt> function can be given an additional string that will
be appended to the returned directory name, as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
const char *MyConfigDir = ConfigDirectory(Name());
</pre></td></tr></table><p>
where <tt>Name()</tt> is the member function of the plugin class that returns the
plugin's name. Again, VDR will make sure that the requested directory will exist
(or return <tt>NULL</tt> in case of an error).
<p>
<b>
The returned string is statically allocated and will be overwritten by subsequent
calls to ConfigDirectory()!
</b>
<p>
The <tt>ConfigDirectory()</tt> function is a static member function of the <tt>cPlugin</tt>
class. This allows it to be called even from outside any member function of the derived
plugin class, by writing
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
const char *MyConfigDir = cPlugin::ConfigDirectory();
</pre></td></tr></table><p>
<a name="Internationalization"><hr><h2>Internationalization</h2>
<center><i><b>Welcome to Babylon!</b></i></center><p>
If a plugin displays texts to the user, it should implement internationalized
versions of these texts and call the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
void RegisterI18n(const tI18nPhrase * const Phrases);
</pre></td></tr></table><p>
to register them with VDR's internationalization mechanism.
<p>
The call to this function must be done in the <a href="#Getting started"><tt>Initialize()</tt></a>
or <a href="#Getting started"><tt>Start()</tt></a> function of the plugin:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
const tI18nPhrase Phrases[] = {
{ "Hello world!",
"Hallo Welt!",
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
"",// TODO
},
{ NULL }
};
void cPluginHello::Start(void)
{
RegisterI18n(Phrases);
}
</pre></td></tr></table><p>
Each entry of type <tt>tI18nPhrase</tt> must have exactly as many members as defined
by the constant <tt>I18nNumLanguages</tt> in the file <tt>VDR/i18n.h</tt>, and the
sequence of the various languages must be the same as defined in <tt>VDR/i18n.c</tt>.<br>
<b>It is very important that the array is terminated with a <tt>{ NULL }</tt>
entry!</b>.
<p>
Usually you won't be able to fill in all the different translations by yourself, so
you may want to contact the maintainers of these languages (listed in the file
<tt>VDR/i18n.c</tt>) and ask them to provide the additional translations.
<p>
The actual runtime selection of the texts corresponding to the selected language
is done by wrapping each internationalized text with the <tt>tr()</tt> macro:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
const char *s = tr("Hello world!");
</pre></td></tr></table><p>
The text given here must be the first one defined in the related <i>Phrases</i>
entry (which is the English version), and the returned pointer is either a translated
version (if available) or the original string. In the latter case a message will be
written to the log file, indicating that a translation is missing.
Texts are first searched for in the <i>Phrases</i> registered for this plugin (if any)
and then in the global VDR texts. So a plugin can make use of texts defined by the
core VDR code.
<a name="Loading plugins into VDR"><hr><h2>Loading plugins into VDR</h2>
<center><i><b>Saddling up!</b></i></center><p>
Plugins are loaded into VDR using the command line option <b><tt>-P</tt></b>, as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
vdr -Phello
</pre></td></tr></table><p>
If the plugin accepts command line options, they are given as part of the argument
to the <b><tt>-P</tt></b> option, which then has to be enclosed in quotes:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
vdr -P"hello -a abc -b"
</pre></td></tr></table><p>
Any number of plugins can be loaded this way, each with its own <b><tt>-P</tt></b> option:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
vdr -P"hello -a abc -b" -Pdvd -Pmp3
</pre></td></tr></table><p>
If you are not starting VDR from the VDR source directory (and thus your plugins
cannot be found at their default location) you need to tell VDR the location of
the plugins through the <b><tt>-L</tt></b> option:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
vdr -L/usr/lib/vdr -Phello
</pre></td></tr></table><p>
There can be any number of <b><tt>-L</tt></b> options, and each of them will apply to the
<b><tt>-P</tt></b> options following it.
<p>
When started with the <b><tt>-h</tt></b> or <b><tt>-V</tt></b> option (for <i>help</i>
or <i>version</i> information, respectively), VDR will automatically load all plugins
in the default or given directory that match the VDR plugin
<a href="#The plugin directory structure">naming convention</a>,
and display their help and/or version information in addition to its own output.
<a name="Building the distribution package"><hr><h2>Building the distribution package</h2>
<center><i><b>Let's get this show on the road!</b></i></center><p>
If you want to make your plugin available to other VDR users, you'll need to
make a package that can be easily distributed.
The <tt>Makefile</tt> that has been created by the call to
<a href="#Initializing a new plugin directory"><tt>newplugin</tt></a>
provides the target <tt>dist</tt>, which does this for you.
<p>
Simply change into your source directory and execute <tt>make dist</tt>:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
cd VDR/PLUGINS/src/hello
make dist
</pre></td></tr></table><p>
After this you should find a file named like
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
vdr-hello-0.0.1.tgz
</pre></td></tr></table><p>
in your source directory, where <tt>hello</tt> will be replaced with your actual
plugin's name, and <tt>0.0.1</tt> will be your plugin's current version number.
<a name="Part II - The Internal Interface"><hr><center><h1>Part II - The Internal Interface</h1></center>
<a name="Status monitor"><hr><h2>Status monitor</h2>
<center><i><b>A piece of the action</b></i></center><p>
If a plugin wants to get informed on various events in VDR, it can derive a class from
<tt>cStatus</tt>, as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/status.h>
class cMyStatusMonitor : public cStatus {
protected:
virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
};
void cMyStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber)
{
if (ChannelNumber)
dsyslog("channel switched to %d on DVB %d", ChannelNumber, Device->CardIndex());
else
dsyslog("about to switch channel on DVB %d", Device->CardIndex());
}
</pre></td></tr></table><p>
An object of this class will be informed whenever the channel is switched on one of
the DVB devices. It could be used in a plugin like this:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/plugin.h>
class cPluginStatus : public cPlugin {
private:
cMyStatusMonitor *statusMonitor;
public:
cPluginStatus(void);
virtual ~cPluginStatus();
...
virtual bool Start(void);
...
};
cPluginStatus::cPluginStatus(void)
{
statusMonitor = NULL;
}
cPluginStatus::~cPluginStatus()
{
delete statusMonitor;
}
bool cPluginStatus::Start(void)
{
statusMonitor = new cMyStatusMonitor;
return true;
}
</pre></td></tr></table><p>
Note that the actual object is created in the <tt>Start()</tt> function, not in the
constructor! It is also important to delete the object in the destructor, in order to
avoid memory leaks.
<p>
A Plugin can implement any number of <tt>cStatus</tt> derived objects, and once
the plugin has been started it may create and delete them as necessary.
No further action apart from creating an object derived from <tt>cStatus</tt>
is necessary. VDR will automatically hook it into a list of status monitors, with
their individual virtual member functions being called in the same sequence as the
objects were created.
<p>
See the file <tt>status.h</tt> for detailed information on which status monitor
member functions are available in <tt>cStatus</tt>. You only need to implement
the functions you actually want to use.
<a name="Players"><hr><h2>Players</h2>
<center><i><b>Play it again, Sam!</b></i></center><p>
Implementing a player is a two step process.
First you need the actual player class, which is derived from the abstract <tt>cPlayer</tt>:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/player.h>
class cMyPlayer : public cPlayer {
protected:
virtual void Activate(bool On);
public:
cMyPlayer(void);
virtual ~cMyPlayer();
};
</pre></td></tr></table><p>
What exactly you do in this class is entirely up to you. If you want to run a separate
thread which, e.g., reads data from a file, you can additionally derive your class from
<tt>cThread</tt> and implement the necessary functionality:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/player.h>
class cMyPlayer : public cPlayer, cThread {
protected:
virtual void Activate(bool On);
virtual void Action(void);
public:
cMyPlayer(void);
virtual ~cMyPlayer();
};
</pre></td></tr></table><p>
Take a look at the files <tt>player.h</tt> and <tt>dvbplayer.c</tt> to see how VDR implements
its own player for the VDR recordings.
<p>
To play the video data, the player needs to call its member function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
int PlayVideo(const uchar *Data, int Length);
</pre></td></tr></table><p>
where <tt>Data</tt> points to a block of <tt>Length</tt> bytes of a PES data
stream. There are no prerequisites regarding the length or alignment of an
individual block of data. The sum of all blocks must simply result in the
desired video data stream, and it must be delivered fast enough so that the
DVB device doesn't run out of data.
To avoid busy loops the player should call its member function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
bool DevicePoll(cPoller &Poller, int TimeoutMs = 0);
</pre></td></tr></table><p>
to determine whether the device is ready for further data.
<p>
If the player can provide more than a single audio track, it can implement the
following functions to make them available:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual int NumAudioTracks(void) const;
virtual const char **GetAudioTracks(int *CurrentTrack = NULL);
virtual void SetAudioTrack(int Index);
</pre></td></tr></table><p>
<p>
If there is an additional audio track that has to be replayed with external hardware,
the player shall call its member function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
void PlayAudio(const uchar *Data, int Length);
</pre></td></tr></table><p>
where <tt>Data</tt> points to a complete audio PES packet of <tt>Length</tt> bytes.
<p>
The second part needed here is a control object that receives user input from the main
program loop and reacts on this by telling the player what to do:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/player.h>
class cMyControl : public cControl {
private:
cMyPlayer *player;
public:
cMyControl(void);
virtual ~cMyControl();
virtual void Hide(void);
virtual eOSState ProcessKey(eKeys Key);
};
</pre></td></tr></table><p>
<tt>cMyControl</tt> shall create an object of type <tt>cMyPlayer</tt> and
hand over a pointer to it to the <tt>cControl</tt> base class, so that it
can be later attached to the primary DVB device:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
cMyControl::cMyControl(void)
:cControl(player = new cMyPlayer)
{
}
</pre></td></tr></table><p>
<tt>cMyControl</tt> will receive the user's key presses through the <tt>ProcessKey()</tt>
function. It will get all button presses, except for the volume control buttons
(<tt>kVolUp</tt>, <tt>kVolDn</tt>, <tt>kMute</tt>), the power button (<tt>kPower</tt>)
and the menu button (<tt>kMenu</tt>). If the user has not pressed a button for a while
(which is typically in the area of about one second), <tt>ProcessKey()</tt> will be called
with <tt>kNone</tt>, so that the <tt>cMyControl</tt> gets a chance to check whether its
player is still active. Once the player has become inactive (because the user has decided
to stop it or the DVB device has detached it), <tt>ProcessKey()</tt> must return <tt>osEnd</tt>
to make the main program loop shut down the player control.
<p>
A derived <tt>cControl</tt> <b>must</b> implement the <tt>Hide()</tt> function, in which
it has to hide itself from the OSD, in case it uses it. <tt>Hide()</tt> may be called at
any time, and it may be called even if the <tt>cControl</tt> is not visible at the moment.
The reason for this is that the <tt>Menu</tt> button shall always bring up the main VDR
menu, so any active <tt>cControl</tt> needs to be hidden when that button is pressed.
<p>
Finally, to get things going, a plugin that implements a player (and the surrounding
infrastructure like displaying a list of playable stuff etc) simply has to call the
static function <tt>cControl::Launch()</tt> with the player control object, as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
cControl::Launch(new cMyControl);
</pre></td></tr></table><p>
Ownership of the <tt>MyControl</tt> object is handed over to the VDR core code,
so the plugin should not keep a pointer to it, because VDR will destroy the object
whenever it sees fit (for instance because a recording shall start that needs to
use the primary DVB device, or the user decides to start a different replay).
<p>
The <tt>cPlayer</tt> class has a member function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
void DeviceStillPicture(const uchar *Data, int Length);
</pre></td></tr></table><p>
which can be called to display a still picture. VDR uses this function when handling
its editing marks. A special case of a "player" might use this function to implement
a "picture viewer".
<p>
For detailed information on how to implement your own player, please take a look
at VDR's <tt>cDvbPlayer</tt> and <tt>cDvbPlayerControl</tt> classes.
<p>
<b>User interface</b>
<p>
In order for a new player to nicely "blend in" to the overall VDR appearance it
is recommended that it implements the same functionality with the same keys as the
VDR player does (as far as this is possible and makes sense). The main points to
consider here are
<ul>
<li>The <i>Ok</i> button shall bring up some display that indicates what is currently
being played, and what the status of this replay session is. As an alternative (for
instance with a DVD player) it may display a player specific menu, from which the
user can select certain options.
<li>The <i>Up</i>, <i>Down</i>, <i>Left</i> and <i>Right</i> buttons shall control
<i>Play</i>, <i>Pause</i>, <i>Fast Rewind</i> and <i>Fast Forward</i>, respectively
(provided that this particular player can implement these functions) if the player
is not currently showing any menu. If there is a menu, they shall allow the user
to navigate in the menu. The dedicated <i>Play</i>, <i>Pause</i>, <i>FastRew</i>
and <i>FastFwd</i> keys shall always result in their specific functionality.
<li>The <i>Green</i> and <i>Yellow</i> buttons shall skip back- and forward by an
amount of time suitable for this player (provided that this particular player can
implement these functions).
<li>The <i>Blue</i> and <i>Stop</i> button shall immediately stop the replay session.
</ul>
Of course, these are only suggestions which should make it easier for VDR users to
enjoy additional players, since they will be able to control them with actions
that they already know. If you absolutely want to do things differently, just go
ahead - it's your show...
<a name="Receivers"><hr><h2>Receivers</h2>
<center><i><b>Tapping into the stream...</b></i></center><p>
In order to receive any kind of data from a <tt>cDevice</tt>, a plugin must set up an
object derived from the <tt>cReceiver</tt> class:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/receiver.h>
class cMyReceiver : public cReceiver, cThread {
protected:
virtual void Activate(bool On);
virtual void Receive(uchar *Data, int Length);
public:
cMyReceiver(int Pid);
};
cMyReceiver::cMyReceiver(int Pid)
:cReceiver(0, -1, 1, Pid)
{
}
void cMyReceiver::Activate(bool On)
{
// start your own thread for processing the received data
}
void cMyReceiver::Receive(uchar *Data, int Length)
{
// buffer the data for processing in a separate thread
}
</pre></td></tr></table><p>
See the comments in <tt>VDR/receiver.h</tt> for details about the various
member functions of <tt>cReceiver</tt>.
<p>
The above example sets up a receiver that wants to receive data from only one
PID (for example the Teletext PID). In order to not interfere with other recording
operations, it sets its priority to <tt>-1</tt> (any negative value will allow
a <tt>cReceiver</tt> to be detached from its <tt>cDevice</tt> at any time.
<p>
Once a <tt>cReceiver</tt> has been created, it needs to be <i>attached</i> to
a <tt>cDevice</tt>:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
cMyReceiver *Receiver = new cMyReceiver(123);
cDevice::ActualDevice()->AttachReceiver(Receiver);
</pre></td></tr></table><p>
Noteh the use of <tt>cDevice::ActualDevice()</tt> here, which makes sure that
the receiver is attached to the device that actually receives the current live
video stream (this may be different from the primary device in case of <i>Transfer
Mode</i>).
<p>
If the <tt>cReceiver</tt> isn't needed any more, it may simply be <i>deleted</i>
and will automatically detach itself from the <tt>cDevice</tt>.
<a name="The On Screen Display"><hr><h2>The On Screen Display</h2>
<center><i><b>Express yourself</b></i></center><p>
Most of the time a plugin should be able to access the OSD through the
standard mechanisms also used by VDR itself. However, these set up the OSD in
a manner of textual rows and columns, and automatically set the various
windows and color depths.
<p>
If a plugin needs to have total control over the OSD, it can call the
static function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/osd.h>
cOsdBase *MyOsd = cOsd::OpenRaw(x, y);
</pre></td></tr></table><p>
where <tt>x</tt> and <tt>y</tt> are the coordinates of the upper left corner
of the OSD area on the screen. Such a "raw" OSD doesn't display anything
yet, so you need to at least call the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
MyOsd->Create(...);
</pre></td></tr></table><p>
to define an actual OSD drawing area (see VDR/osdbase.h for the declarations
of these functions, and VDR/osd.c to see how VDR opens the OSD and sets up
its windows and color depths).
<a name="Devices"><hr><h2>Devices</h2>
<center><i><b>Expanding the possibilities</b></i></center><p>
By default VDR is based on using DVB PCI cards that are supported by the
LinuxDVB driver. However, a plugin can implement additional devices that
can be used as sources of MPEG data for viewing or recording, and also
as output devices for replaying. Such a device can be a physical card
that is installed in the PC (like, for instance, an MPEG encoder card that
allows the analog signal of a proprietary set-top box to be integrated
into a VDR system; or an analog TV receiver card, which does the MPEG encoding
"on the fly" - assuming your machine is fast enough), or just a software program that takes an MPEG data
stream and displays it, for instance, on an existing graphics adapter.
<p>
To implement an additional device, a plugin must derive a class from cDevice:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/device.h>
class cMyDevice : public cDevice {
...
};
</pre></td></tr></table><p>
The derived class must implement several virtual functions, according to
the abilities this new class of devices can provide. See the comments in the
file <tt>VDR/device.h</tt> for more information on the various functions,
and also <tt>VDR/dvbdevice.[hc]</tt> for details on the implementation of
the <tt>cDvbDevice</tt>, which is used to access the DVB PCI cards.
<p>
<b>Channel selection</b>
<p>
If the new device can receive, it most likely needs to provide a way of
selecting which channel it shall tune to:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool ProvidesSource(int Source) const;
virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL);
virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView);
</pre></td></tr></table><p>
These functions will be called with the desired source or channel and shall return whether
this device can provide the requested source or channel and whether tuning to it was successful,
repectively.
<p>
<b>Audio selection</b>
<p>
If the device can provide more than a single audio track, it can implement the
following functions to make them available:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual int NumAudioTracksDevice(void) const;
virtual const char **GetAudioTracksDevice(int *CurrentTrack = NULL) const;
virtual void SetAudioTrackDevice(int Index);
</pre></td></tr></table><p>
<p>
<b>Recording</b>
<p>
A device that can be used for recording must implement the functions
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool SetPid(cPidHandle *Handle, int Type, bool On);
virtual bool OpenDvr(void);
virtual void CloseDvr(void);
virtual bool GetTSPacket(uchar *&Data);
</pre></td></tr></table><p>
which allow VDR to set the PIDs that shall be recorded, set up the device for
recording (and shut it down again), and receive the MPEG data stream. The data
must be delivered in the form of a Transport Stream (TS), which consists of
packets that are all 188 bytes in size. Each call to <tt>GetTSPacket()</tt>
must deliver exactly one such packet (if one is currently available).
<p>
If this device allows receiving several different data streams, it can
implement
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool CanBeReUsed(int Frequency, int Vpid);
</pre></td></tr></table><p>
to indicate this to VDR.
<p>
<b>Replaying</b>
<p>
The functions to implement replaying capabilites are
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool HasDecoder(void) const;
virtual bool CanReplay(void) const;
virtual bool SetPlayMode(ePlayMode PlayMode);
virtual void TrickSpeed(int Speed);
virtual void Clear(void);
virtual void Play(void);
virtual void Freeze(void);
virtual void Mute(void);
virtual void StillPicture(const uchar *Data, int Length);
virtual bool Poll(cPoller &Poller, int TimeoutMs = 0);
virtual int PlayVideo(const uchar *Data, int Length);
</pre></td></tr></table><p>
In addition, the following functions may be implemented to provide further
functionality:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int Si
virtual void SetVideoFormat(bool VideoFormat16_9);
virtual void SetVolumeDevice(int Volume);
</pre></td></tr></table><p>
<p>
<b>On Screen Display</b>
<p>
If your device provides On Screen Display (OSD) capabilities (which every device
that is supposed to be used as a primary device should do), it can implement
the function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual cOsdBase *NewOsd(int x, int y);
</pre></td></tr></table><p>
which must return a newly created object of a derived cOsdBase class that
implements the functions necessary to display OSD information on your device.
The caller of this function will delete the object as soon as it is no longer
needed.
<p>
<b>Initializing new devices</b>
<p>
A derived cDevice class shall implement a static function
in which it determines whether the necessary hardware to run this sort of
device is actually present in this machine (or whatever other prerequisites
might be important), and then creates as many device objects as necessary.
See <tt>VDR/dvbdevice.c</tt> for the implementation of the <tt>cDvbDevice</tt>
initialize function.
<p>
A plugin that adds devices to a VDR instance shall call this
function from its <a href="#Getting started"><tt>Initialize()</tt></a> function
to make sure other plugins that may need to have access to all available devices
will see them in their <a href="#Getting started"><tt>Start()</tt></a> function.
<p>
Nothing needs to be done to shut down the devices. VDR will automatically
shut down (delete) all devices when the program terminates. It is therefore
important that the devices are created on the heap, using the <tt>new</tt>
operator!
<a name="Dolby Digital"><hr><h2>Dolby Digital</h2>
<center><i><b>"The stereo effect may only be experienced if stereo equipment is used!"</b></i></center><p>
There are many different ways to replay additional audio tracks, like Dolby Digital.
So VDR offers a plugin interface that allows for the implementation of any kind of
audio replay facility.
<p>
To implement a new audio output facility, simply derive a class from <tt>cAudio</tt>,
as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/audio.h>
#include <vdr/thread.h>
class cMyAudio : public cAudio, private cThread {
private:
virtual void Action(void);
public:
cMyAudio(void);
virtual void Play(const uchar *Data, int Length);
virtual void Mute(bool On);
virtual void Clear(void);
};
</pre></td></tr></table><p>
You should create your derived audio object in the
<a href="#Getting started"><tt>Start()</tt></a> function of your plugin.
Note that the object has to be created on the heap (using <tt>new</tt>),
and you shall not delete it at any point (it will be deleted automatically
when the program ends).
<p>
The <tt>Play()</tt> function will be offered complete audio PES packets
and has to accept each packet immediately. It must return as soon as possible,
in order to not delay the overall replay process. Therefore you may want to
also derive your class from <tt>cThread</tt> and run the actual audio processing
as a separate thread. Note that the offered data is only valid within the call
to <tt>Play()</tt>, so if you can't process the entire block immediately, you
will need to copy it for later processing in your thread.
<p>
The <tt>Mute()</tt> and <tt>Clear()</tt> functions will be called whenever the audio shall
be muted, or any buffered data shall be cleared, respectively.
<a name="Remote Control"><hr><h2>Remote Control</h2>
<center><i><b>The joy of zapping!</b></i></center><p>
There are several ways to control the operation of VDR. The builtin methods
are using the PC keyboard, a homebuilt RCU unit or the LIRC interface.
Of course there may be many more ways you might think of to implement a
remote control, so a plugin can use the <tt>cRemote</tt> class to do that.
<p>
The simplest method for a plugin to issue commands to VDR is to call the
static function <tt>cRemote::Put(eKeys Key)</tt>, as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
cRemote::Put(kUp);
</pre></td></tr></table><p>
In this case the plugin must do the mapping of whatever incoming signal or code
it processes to the <tt>eKeys</tt> values itself. This makes sense if the incoming
codes are well known and won't ever change.
<p>
In cases where the incoming codes are not known, or not all available keys may
be supported by the actual remote control in use, you may want to derive your
own remote control class from <tt>cRemote</tt>, as in
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
#include <vdr/remote.h>
#include <vdr/thread.h>
class cMyRemote : public cRemote, private cThread {
private:
virtual void Action(void);
public:
cMyRemote(const char *Name);
virtual bool Initialize(void);
};
</pre></td></tr></table><p>
Note that deriving from <tt>cThread</tt> is not required for a remote control
class to work, but typically you may want to have a separate thread running that
collects the input and delivers it to the <tt>cRemote</tt> base class.
<p>
You should create your derived remote control object in the
<a href="#Getting started"><tt>Start()</tt></a> function of your plugin.
Note that the object has to be created on the heap (using <tt>new</tt>),
and you shall not delete it at any point (it will be deleted automatically
when the program ends).
<p>
The constructor of your remote control class should look like this
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
cMyRemote::cMyRemote(const char *Name)
:cRemote(Name)
{
Start();
}
</pre></td></tr></table><p>
The <tt>Name</tt> is important in order for the <tt>cRemote</tt> base class
to be able to distinguish the codes for the various remote controls.
When creating your <tt>cMyRemote</tt> object you should use the value returned
by the <tt>Name()</tt> member function of the plugin class, which returns the
plugin's name. Calling <tt>Start()</tt> will start the thread that collects
the incoming data (by calling your <tt>Action()</tt> function).
In case you need to do any other setup steps, like opening a file or initializing
member variables, you should do so before calling <tt>Start()</tt>.
<p>
If your remote control for some reason can't work (maybe because it was unable to
open some file handle it requires) it can implement the virtual function
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
virtual bool Ready(void);
</pre></td></tr></table><p>
and have it return <i>false</i>. In that case VDR will not try to learn keys from
that remote control.
VDR will handle everything necessary to learn the key mappings of your remote
control. In order to do so, it will first call the virtual function <tt>Initialize()</tt>,
in which you should take all necessary steps to make sure your remote control
can be accessed. This may, for instance, include trying various communications
protocols. <tt>Initialize()</tt>, if implemented, shall only return after it has
made sure data can be received from the remote control. Before calling this
function, VDR will prompt the user on the OSD to press any key on the remote control.
As soon as your derived <tt>cRemote</tt> class has detected useful incoming data,
<tt>Initialize()</tt> should return <i>true</i>. If any fatal error occurs, <i>false</i>
should be returned.
<p>
If your remote control class needs some setup data that shall be
readily available next time VDR starts (without having to go through the initialization
procedure again) it can use the <tt>cRemote</tt> member functions
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
void PutSetup(const char *Setup);
const char *GetSetup(void);
</pre></td></tr></table><p>
to store and retrieve a character string containing whatever data is needed.
Note that the <tt>Initialize()</tt> function will only be called if there are
no key mappings known for this remote control. Once the key mappings have been
learned, <tt>Initialize()</tt> will never be called again.
<p>
The <tt>cRemote</tt> class assumes that any incoming remote control code can be
expressed as a character string. So whatever data your remote control provides
needs to be given to the base class by calling
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
Put(const char *Code, bool Repeat = false, bool Release = false);
</pre></td></tr></table><p>
where <tt>Code</tt> is the string representation of the remote control's
incoming data. <tt>Repeat</tt> and <tt>Release</tt> are boolean flags that
indicate whether this is a repeated keypress, or the key has been released.
Since a common case for remote control data is to be given as a numerical
value, there is another <tt>Put()</tt> function available for your convenience,
which takes a 64 bit unsigned integer value instead of a character string:
<p><table><tr><td bgcolor=#F0F0F0><pre><br>
Put(uint64 Code, bool Repeat = false, bool Release = false);
</pre></td></tr></table><p>
The other parameters have the same meaning as in the first version of this function.
</body>
</html>
|