Display media captures files (recordings)

This commit is contained in:
Julien Wadel 2023-01-12 16:33:28 +01:00
parent 62c72bd0e5
commit 5dc002197d
47 changed files with 1558 additions and 28 deletions

View file

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Resizeable on mouse's wheel.
* Reset on mouse's right click (first for size if changed, second for position)
- Hide the active speaker from the mini views.
- Display recordings list from the burger menu.
### Fixed
- Mini views layout on actives speaker.

View file

@ -212,6 +212,7 @@ set(SOURCES
src/components/core/event-count-notifier/AbstractEventCountNotifier.cpp
src/components/file/FileDownloader.cpp
src/components/file/FileExtractor.cpp
src/components/file/FileMediaModel.cpp
src/components/friend/FriendListListener.cpp
src/components/history/HistoryModel.cpp
src/components/history/HistoryProxyModel.cpp
@ -246,6 +247,8 @@ set(SOURCES
src/components/presence/Presence.cpp
src/components/recorder/RecorderManager.cpp
src/components/recorder/RecorderModel.cpp
src/components/recorder/RecordingListModel.cpp
src/components/recorder/RecordingProxyModel.cpp
src/components/search/SearchListener.cpp
src/components/search/SearchResultModel.cpp
src/components/search/SearchSipAddressesModel.cpp
@ -348,6 +351,7 @@ set(HEADERS
src/components/core/event-count-notifier/AbstractEventCountNotifier.hpp
src/components/file/FileDownloader.hpp
src/components/file/FileExtractor.hpp
src/components/file/FileMediaModel.hpp
src/components/friend/FriendListListener.hpp
src/components/history/HistoryModel.hpp
src/components/history/HistoryProxyModel.hpp
@ -383,6 +387,8 @@ set(HEADERS
src/components/presence/Presence.hpp
src/components/recorder/RecorderManager.hpp
src/components/recorder/RecorderModel.hpp
src/components/recorder/RecordingListModel.hpp
src/components/recorder/RecordingProxyModel.hpp
src/components/search/SearchListener.hpp
src/components/search/SearchResultModel.hpp
src/components/search/SearchSipAddressesModel.hpp

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="80"
height="80"
viewBox="0 0 80 80"
version="1.1"
id="svg19"
sodipodi:docname="menu_recordings.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs23" />
<sodipodi:namedview
id="namedview21"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="8.9569892"
inkscape:cx="33.716687"
inkscape:cy="46.444178"
inkscape:window-width="1920"
inkscape:window-height="1163"
inkscape:window-x="1920"
inkscape:window-y="500"
inkscape:window-maximized="1"
inkscape:current-layer="svg19" />
<title
id="title2">menu_recordings@3x</title>
<g
id="menu_recordings"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
transform="matrix(0.43010753,0,0,0.43010753,22.5,20)">
<path
d="m 44,49.408775 c 0,1.380712 -1.119288,2.5 -2.5,2.5 -1.380712,0 -2.5,-1.119288 -2.5,-2.5 V 15.858977 C 39,9.8671957 34.088652,5 28.019919,5 21.953212,5 17.041545,9.8677246 17.041545,15.858977 v 33.549798 c 0,5.988072 4.911888,10.854733 10.978374,10.854733 1.380712,0 2.5,1.119288 2.5,2.5 0,1.380712 -1.119288,2.5 -2.5,2.5 -8.81761,0 -15.978374,-7.094831 -15.978374,-15.854733 V 15.858977 C 12.041545,7.0961924 19.201797,0 28.019919,0 36.840019,0 44,7.0956142 44,15.858977 Z"
fill="#444445"
id="path4" />
<polygon
fill="#444445"
points="30.51992,72.988783 25.51992,72.988783 25.51992,88.739626 30.51992,88.739626 "
id="polygon6" />
<path
d="m 30.519919,72.988783 v 15.750843 c 0,3.333334 -5,3.333334 -5,0 V 72.988783 c 0,-3.333334 5,-3.333334 5,0 z"
fill="#444445"
id="path8" />
<polygon
fill="#444445"
points="21.078967,88 21.078967,93 34.404614,93 34.404614,88 "
id="polygon10" />
<path
d="m 21.078967,88 h 13.325647 c 3.333333,0 3.333333,5 0,5 H 21.078967 c -3.333333,0 -3.333333,-5 0,-5 z"
fill="#444445"
id="path12" />
<path
d="m 0,33.123199 c 0,-1.380712 1.1192881,-2.5 2.5,-2.5 1.3807119,0 2.5,1.119288 2.5,2.5 v 14.574432 c 0,12.583093 10.301683,22.791152 23.020773,22.791152 1.380712,0 2.5,1.119288 2.5,2.5 0,1.380712 -1.119288,2.5 -2.5,2.5 C 12.550426,75.488783 0,63.052418 0,47.697631 Z"
fill="#444445"
id="path14" />
<path
d="M 44,85.026212 62.729894,73.500726 44,61.974034 Z m -1.189707,-29.655327 26,16.000838 c 1.586319,0.976247 1.586265,3.282118 -9.9e-5,4.258291 l -26,15.999162 C 41.144517,92.654154 39,91.455776 39,89.5 v -32 c 0,-1.955828 2.14462,-3.154199 3.810293,-2.129115 z"
fill="#444445"
fill-rule="nonzero"
id="path16" />
</g>
<metadata
id="metadata840">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:title>menu_recordings@3x</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -1792,6 +1792,11 @@ Klik her: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished">Tjek for opdateringer</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Klik her: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished">Tjek for opdateringer</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Klik her: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1792,6 +1792,11 @@ Klicken Sie hier: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Auf Aktualisierungen prüfen</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Klicken Sie hier: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Auf Aktualisierungen prüfen</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Klicken Sie hier: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation>Automatisch</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1792,6 +1792,11 @@ Click here: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Check for updates</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation>Recordings</translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Click here: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Check for updates</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation>Recordings</translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Click here: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation>Auto</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation>No recordings</translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation>Vocal</translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation>Are you sure you want to delete this item?</translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1792,6 +1792,11 @@ Haga clic aquí: &lt;a href=&quot;%1&quot;&gt;%1 &lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished">Buscar actualizaciones</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Haga clic aquí: &lt;a href=&quot;%1&quot;&gt;%1 &lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished">Buscar actualizaciones</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Haga clic aquí: &lt;a href=&quot;%1&quot;&gt;%1 &lt;/a&gt;
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1792,6 +1792,11 @@ Cliquez ici : &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Vérifier les mises à jour</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Cliquez ici : &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Vérifier les mises à jour</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Cliquez ici : &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation>Auto</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1782,6 +1782,11 @@ Kattintson ide: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Frissítések keresése</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1802,6 +1807,11 @@ Kattintson ide: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Frissítések keresése</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2279,6 +2289,24 @@ Kattintson ide: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation>Önműködő</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1792,6 +1792,11 @@ Clicca: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Controlla aggiornamenti</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Clicca: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Controlla aggiornamenti</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Clicca: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation>Automatico</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1782,6 +1782,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1802,6 +1807,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2279,6 +2289,24 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1802,6 +1802,11 @@ Spustelėkite čia: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1822,6 +1827,11 @@ Spustelėkite čia: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2305,6 +2315,24 @@ Spustelėkite čia: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1792,6 +1792,11 @@ Clique aqui: &lt;a href=&quot;%1&quot;&gt;%1 &lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Verifique se atualizações</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Clique aqui: &lt;a href=&quot;%1&quot;&gt;%1 &lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished">Verifique se atualizações</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Clique aqui: &lt;a href=&quot;%1&quot;&gt;%1 &lt;/a&gt;
<translation>Auto</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1802,6 +1802,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Проверить обновления</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1822,6 +1827,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Проверить обновления</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2305,6 +2315,24 @@
<translation>Авто</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1792,6 +1792,11 @@ Klicka här: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1812,6 +1817,11 @@ Klicka här: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2292,6 +2302,24 @@ Klicka här: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1782,6 +1782,11 @@ Buraya tıklayın: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Güncellemeleri denetle</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1802,6 +1807,11 @@ Buraya tıklayın: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation>Güncellemeleri denetle</translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2279,6 +2289,24 @@ Buraya tıklayın: &lt;a href=&quot;%1&quot;&gt;%1&lt;/a&gt;
<translation>Kendiliğinden</translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1802,6 +1802,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1822,6 +1827,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2305,6 +2315,24 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -1782,6 +1782,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindowTopMenuBar</name>
@ -1802,6 +1807,11 @@
<extracomment>&apos;Check for updates&apos; : Item menu for checking updates</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordings</source>
<extracomment>&apos;Recordings&apos; : Label for the recordings menu.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ManageAccounts</name>
@ -2279,6 +2289,24 @@
<translation></translation>
</message>
</context>
<context>
<name>Recordings</name>
<message>
<source>titleNoRecordings</source>
<extracomment>&apos;No recordings&apos; : Title of an empty list of records.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsVocalLabel</source>
<extracomment>&apos;Vocal&apos; : Label for recording type that is a vocal message.</extracomment>
<translation type="unfinished"></translation>
</message>
<message>
<source>recordingsDelete</source>
<extracomment>&apos;Are you sure you want to delete this item?&apos; : Confirmation message for removing a record.</extracomment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsAdvanced</name>
<message>

View file

@ -135,6 +135,7 @@
<file>assets/images/play_custom.svg</file>
<file>assets/images/recording_sign.svg</file>
<file>assets/images/record_custom.svg</file>
<file>assets/images/recordings_custom.svg</file>
<file>assets/images/remove_participant_custom.svg</file>
<file>assets/images/screen_sharing_custom.svg</file>
<file>assets/images/screenshot_custom.svg</file>
@ -481,6 +482,7 @@
<file>ui/views/App/Main/MainWindowMenuBar.qml</file>
<file>ui/views/App/Main/MainWindow.qml</file>
<file>ui/views/App/Main/MainWindowTopMenuBar.qml</file>
<file>ui/views/App/Main/Recordings.qml</file>
<file>ui/views/App/Settings/Dialogs/SettingsLdapEdit.qml</file>
<file>ui/views/App/Settings/Dialogs/SettingsSipAccountsEdit.js</file>
<file>ui/views/App/Settings/Dialogs/SettingsSipAccountsEdit.qml</file>
@ -533,6 +535,7 @@
<file>ui/views/App/Styles/Main/InviteFriendsStyle.qml</file>
<file>ui/views/App/Styles/Main/HistoryViewStyle.qml</file>
<file>ui/views/App/Styles/Main/MainWindowStyle.qml</file>
<file>ui/views/App/Styles/Main/RecordingsStyle.qml</file>
<file>ui/views/App/Styles/qmldir</file>
<file>ui/views/App/Styles/Settings/Dialogs/SettingsSipAccountsEditStyle.qml</file>
<file>ui/views/App/Styles/Settings/Dialogs/SettingsVideoPreviewStyle.qml</file>

View file

@ -676,6 +676,7 @@ void App::registerTypes () {
registerType<HistoryProxyModel>("HistoryProxyModel");
registerType<LdapProxyModel>("LdapProxyModel");
registerType<ParticipantImdnStateProxyModel>("ParticipantImdnStateProxyModel");
registerType<RecordingProxyModel>("RecordingProxyModel");
registerType<SipAddressesProxyModel>("SipAddressesProxyModel");
registerType<SearchSipAddressesModel>("SearchSipAddressesModel");
registerType<SearchSipAddressesProxyModel>("SearchSipAddressesProxyModel");
@ -711,6 +712,7 @@ void App::registerTypes () {
registerUncreatableType<ContactsImporterModel>("ContactsImporterModel");
registerUncreatableType<ContentModel>("ContentModel");
registerUncreatableType<ContentListModel>("ContentListModel");
registerUncreatableType<FileMediaModel>("FileMediaModel");
registerUncreatableType<HistoryModel>("HistoryModel");
registerUncreatableType<LdapModel>("LdapModel");
registerUncreatableType<RecorderModel>("RecorderModel");

View file

@ -86,16 +86,18 @@ public:
virtual bool remove(QObject *itemToRemove) override{
bool removed = false;
qInfo() << QStringLiteral("Removing ") << itemToRemove->metaObject()->className() << QStringLiteral(" : ") << itemToRemove;
int index = 0;
for(auto item : mList)
if( item == itemToRemove) {
removed = removeRow(index);
break;
}else
++index;
if( !removed)
qWarning() << QStringLiteral("Unable to remove ") << itemToRemove->metaObject()->className() << QStringLiteral(" : ") << itemToRemove;
if(itemToRemove){
qInfo() << QStringLiteral("Removing ") << itemToRemove->metaObject()->className() << QStringLiteral(" : ") << itemToRemove;
int index = 0;
for(auto item : mList)
if( item == itemToRemove) {
removed = removeRow(index);
break;
}else
++index;
if( !removed)
qWarning() << QStringLiteral("Unable to remove ") << itemToRemove->metaObject()->className() << QStringLiteral(" : ") << itemToRemove;
}
return removed;
}
virtual bool remove(QSharedPointer<QObject> itemToRemove){

View file

@ -54,6 +54,7 @@
#include "core/CoreManager.hpp"
#include "file/FileDownloader.hpp"
#include "file/FileExtractor.hpp"
#include "file/FileMediaModel.hpp"
#include "history/HistoryProxyModel.hpp"
#include "ldap/LdapModel.hpp"
#include "ldap/LdapListModel.hpp"
@ -71,6 +72,8 @@
#include "presence/OwnPresenceModel.hpp"
#include "recorder/RecorderModel.hpp"
#include "recorder/RecorderManager.hpp"
#include "recorder/RecordingListModel.hpp"
#include "recorder/RecordingProxyModel.hpp"
#include "settings/AccountSettingsModel.hpp"
#include "settings/SettingsModel.hpp"
#include "search/SearchResultModel.hpp"

View file

@ -1316,7 +1316,7 @@ QString CallModel::generateSavedFilename () const {
QString CallModel::generateSavedFilename (const QString &from, const QString &to) {
auto escape = [](const QString &str) {
constexpr char ReservedCharacters[] = "[<|>|:|\"|/|\\\\|\\?|\\*|\\+|\\|]+";
constexpr char ReservedCharacters[] = "[<|>|:|\"|/|\\\\|\\?|\\*|\\+|\\||_|-]+";
static QRegularExpression regexp(ReservedCharacters);
return QString(str).replace(regexp, "");
};
@ -1325,3 +1325,35 @@ QString CallModel::generateSavedFilename (const QString &from, const QString &to
.arg(escape(from))
.arg(escape(to));
}
QStringList CallModel::splitSavedFilename(const QString& filename){
QStringList fields = filename.split('_');
if(fields.size() == 4 && fields[0].split('-').size() == 3 && fields[1].split('-').size() == 3){
return fields;
}else
return QStringList(filename);
}
QDateTime CallModel::getDateTimeSavedFilename(const QString& filename){
auto fields = splitSavedFilename(filename);
if(fields.size() > 1)
return QDateTime::fromString(fields[0] + "_" +fields[1], "yyyy-MM-dd_hh-mm-ss");
else
return QDateTime();
}
QString CallModel::getFromSavedFilename(const QString& filename){
auto fields = splitSavedFilename(filename);
if(fields.size() > 1)
return fields[2];
else
return "";
}
QString CallModel::getToSavedFilename(const QString& filename){
auto fields = splitSavedFilename(filename);
if(fields.size() > 1)
return fields[3];
else
return "";
}

View file

@ -320,8 +320,12 @@ public:
QString generateSavedFilename () const;
// Format : Date_Time_From_To
static QString generateSavedFilename (const QString &from, const QString &to);
static QStringList splitSavedFilename(const QString& filename);// If doesn't match to generateSavedFilename, return filename
static QDateTime getDateTimeSavedFilename(const QString& filename);
static QString getFromSavedFilename(const QString& filename);
static QString getToSavedFilename(const QString& filename);
private:
void connectTo(CallListener * listener);

View file

@ -27,6 +27,7 @@
#include "components/core/CoreManager.hpp"
#include "components/participant/ParticipantDeviceModel.hpp"
#include "components/settings/SettingsModel.hpp"
#include "components/sound-player/SoundPlayer.hpp"
#include "Camera.hpp"
#include "CameraDummy.hpp"
@ -104,6 +105,9 @@ void Camera::resetWindowId() const{
oldRenderer = (QQuickFramebufferObject::Renderer *)core->getNativeVideoWindowId();
if(oldRenderer)
core->setNativeVideoWindowId(NULL);
}else if(mWindowIdLocation == Player){
if(mLinphonePlayer && mLinphonePlayer->getLinphonePlayer())
mLinphonePlayer->getLinphonePlayer()->setWindowId(nullptr);
}
qDebug() << "[Camera] Removed " << oldRenderer << " at " << mWindowIdLocation << " for " << this;
mIsWindowIdSet = false;
@ -148,6 +152,9 @@ void Camera::updateWindowIdLocation(){
setWindowIdLocation(WindowIdLocation::Device);
useDefaultWindow = false;
}
}else if( mLinphonePlayer){
setWindowIdLocation(WindowIdLocation::Player);
useDefaultWindow = false;
}
if(useDefaultWindow){
setWindowIdLocation(WindowIdLocation::Core);
@ -163,6 +170,10 @@ void Camera::removeCallModel(){
mCallModel = nullptr;
}
void Camera::removeLinphonePlayer(){
mLinphonePlayer = nullptr;
}
QQuickFramebufferObject::Renderer *Camera::createRenderer () const {
QQuickFramebufferObject::Renderer * renderer = NULL;
if(mWindowIdLocation == CorePreview){
@ -194,6 +205,14 @@ QQuickFramebufferObject::Renderer *Camera::createRenderer () const {
renderer = (QQuickFramebufferObject::Renderer *) CoreManager::getInstance()->getCore()->createNativeVideoWindowId();
if(renderer)
CoreManager::getInstance()->getCore()->setNativeVideoWindowId(renderer);
}else if( mWindowIdLocation == Player){
auto player = mLinphonePlayer->getLinphonePlayer();
if(player){
qDebug() << "[Camera] Setting Camera to Player";
renderer = (QQuickFramebufferObject::Renderer *) player->createWindowId();
if(renderer)
player->setWindowId(renderer);
}
}
if( !renderer){
QTimer::singleShot(1, this, &Camera::isNotReady);// Workaround for const createRenderer
@ -227,6 +246,10 @@ ParticipantDeviceModel * Camera::getParticipantDeviceModel() const{
return mParticipantDeviceModel;
}
SoundPlayer * Camera::getLinphonePlayer() const{
return mLinphonePlayer;
}
void Camera::setCallModel (CallModel *callModel) {
if (mCallModel != callModel) {
if( mCallModel){
@ -275,6 +298,17 @@ void Camera::setParticipantDeviceModel(ParticipantDeviceModel * participantDevic
emit participantDeviceModelChanged(mParticipantDeviceModel);
}
}
void Camera::setLinphonePlayer(SoundPlayer *player){
if (mLinphonePlayer!= player) {
if( mLinphonePlayer)
disconnect(mLinphonePlayer, &QObject::destroyed, this, &Camera::removeLinphonePlayer);
mLinphonePlayer = player;
connect(mLinphonePlayer, &QObject::destroyed, this, &Camera::removeLinphonePlayer);
updateWindowIdLocation();
update();
emit linphonePlayerChanged(mLinphonePlayer);
}
}
void Camera::isReady(){
setIsReady(true);

View file

@ -27,6 +27,8 @@
#include <QMutex>
#include <QTimer>
#include "components/sound-player/SoundPlayer.hpp"
// =============================================================================
namespace linphone {
@ -35,6 +37,7 @@ namespace linphone {
class CallModel;
class ParticipantDeviceModel;
// -----------------------------------------------------------------------------
class Camera : public QQuickFramebufferObject {
@ -44,12 +47,14 @@ class Camera : public QQuickFramebufferObject {
Q_PROPERTY(ParticipantDeviceModel * participantDeviceModel READ getParticipantDeviceModel WRITE setParticipantDeviceModel NOTIFY participantDeviceModelChanged)
Q_PROPERTY(bool isPreview READ getIsPreview WRITE setIsPreview NOTIFY isPreviewChanged);
Q_PROPERTY(bool isReady READ getIsReady WRITE setIsReady NOTIFY isReadyChanged);
Q_PROPERTY(SoundPlayer * linphonePlayer READ getLinphonePlayer WRITE setLinphonePlayer NOTIFY linphonePlayerChanged)
typedef enum{
None = -1,
CorePreview = 0,
Call,
Device,
Player,
Core
}WindowIdLocation;
@ -77,24 +82,28 @@ signals:
void participantDeviceModelChanged(ParticipantDeviceModel *participantDeviceModel);
void requestNewRenderer();
void videoDefinitionChanged();
void linphonePlayerChanged(SoundPlayer * linphonePlayer);
private:
CallModel *getCallModel () const;
bool getIsPreview () const;
bool getIsReady () const;
ParticipantDeviceModel * getParticipantDeviceModel() const;
SoundPlayer * getLinphonePlayer() const;
void setCallModel (CallModel *callModel);
void setIsPreview (bool status);
void setIsReady(bool status);
void setParticipantDeviceModel(ParticipantDeviceModel * participantDeviceModel);
void setWindowIdLocation(const WindowIdLocation& location);
void setLinphonePlayer(SoundPlayer *player);
void setWindowIdLocation(const WindowIdLocation& location);
void activatePreview();
void deactivatePreview();
void updateWindowIdLocation();
void removeParticipantDeviceModel();
void removeCallModel();
void removeLinphonePlayer();
QVariantMap mLastVideoDefinition;
QTimer mLastVideoDefinitionChecker;
@ -103,6 +112,7 @@ private:
bool mIsReady = false;
CallModel *mCallModel = nullptr;
ParticipantDeviceModel *mParticipantDeviceModel = nullptr;
SoundPlayer * mLinphonePlayer = nullptr;
WindowIdLocation mWindowIdLocation = None;
mutable bool mIsWindowIdSet = false;

View file

@ -0,0 +1,151 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FileMediaModel.hpp"
#include <QQmlApplicationEngine>
#include "app/App.hpp"
#include "components/call/CallModel.hpp"
#include "components/recorder/RecorderModel.hpp"
#include "components/sound-player/SoundPlayer.hpp"
// =============================================================================
FileMediaModel::FileMediaModel (const QString& path, QObject * parent) :mFileInfo(path), QObject(parent) {
if(path.isEmpty()) return;
init();
}
FileMediaModel::FileMediaModel (const QFileInfo& fileInfo, QObject * parent) :mFileInfo(fileInfo), QObject(parent) {
init();
}
FileMediaModel::~FileMediaModel(){
}
QSharedPointer<FileMediaModel> FileMediaModel::create(const QString& path){
return FileMediaModel::create(QFileInfo(path));
}
QSharedPointer<FileMediaModel> FileMediaModel::create(const QFileInfo& fileInfo){
auto model = QSharedPointer<FileMediaModel>::create(fileInfo);
return model;
}
void FileMediaModel::init(){
QString baseName = getBaseName();
SoundPlayer soundPlayer;
soundPlayer.setSource(mFileInfo.absoluteFilePath());
if(soundPlayer.open()){
mDuration = soundPlayer.getDuration();
if(CallModel::splitSavedFilename(baseName).size() > 1)
mType = IS_CALL_RECORD;
else if( RecorderModel::splitSavedFilename(baseName).size() > 1)
mType = IS_VOICE_RECORD;
else
mType = IS_PLAYABLE;
}else if(CallModel::splitSavedFilename(baseName).size() > 1)
mType = IS_SNAPSHOT;
else
mType = IS_UNKNOWN;
}
// -----------------------------------------------------------------------------
QString FileMediaModel::getBaseName() const{
return mFileInfo.baseName();
}
QString FileMediaModel::getFilePath() const{
return mFileInfo.absoluteFilePath();
}
int FileMediaModel::getDuration() const{
return mDuration;
}
FileMediaModel::FILE_TYPE FileMediaModel::getType() const{
return mType;
}
QString FileMediaModel::getFrom()const{
QString baseName = getBaseName();
switch(mType){
case IS_CALL_RECORD: case IS_SNAPSHOT:
return CallModel::getFromSavedFilename(baseName);
break;
case IS_VOICE_RECORD:
return "";
break;
default:{
return "";
}
}
}
QString FileMediaModel::getTo()const{
QString baseName = getBaseName();
switch(mType){
case IS_CALL_RECORD: case IS_SNAPSHOT:
return CallModel::getToSavedFilename(baseName);
break;
case IS_VOICE_RECORD:
return "";
break;
default:{
return "";
}
}
}
QDateTime FileMediaModel::getCreationDateTime() const{
QString baseName;
switch(mType){
case IS_CALL_RECORD: case IS_SNAPSHOT:
baseName = getBaseName();
return CallModel::getDateTimeSavedFilename(baseName);
break;
case IS_VOICE_RECORD:
baseName = getBaseName();
return RecorderModel::getDateTimeSavedFilename(baseName);
break;
default:{
QDateTime creationDate = mFileInfo.birthTime();
return creationDate.isValid() ? creationDate : mFileInfo.lastModified();
}
}
}
QStringList FileMediaModel::getParsedBaseName() const{
QString baseName = getBaseName();
switch(mType){
case IS_CALL_RECORD: case IS_SNAPSHOT:
return CallModel::splitSavedFilename(baseName);
break;
case IS_VOICE_RECORD:
return RecorderModel::splitSavedFilename(baseName);
break;
default:{
return QStringList(baseName);
}
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILE_MEDIA_MODEL_H_
#define FILE_MEDIA_MODEL_H_
#include <QDateTime>
#include <QObject>
#include <QFileInfo>
#include <QString>
#include <QSharedPointer>
// =============================================================================
class FileMediaModel : public QObject {
Q_OBJECT
Q_PROPERTY(QString baseName READ getBaseName CONSTANT)
Q_PROPERTY(QStringList parsedBaseName READ getParsedBaseName CONSTANT)
Q_PROPERTY(QString filePath READ getFilePath CONSTANT)
Q_PROPERTY(QDateTime creationDateTime READ getCreationDateTime CONSTANT)
Q_PROPERTY(FILE_TYPE type READ getType CONSTANT)
// App Custom
Q_PROPERTY(int duration READ getDuration CONSTANT)
Q_PROPERTY(QString from READ getFrom CONSTANT)
Q_PROPERTY(QString to READ getTo CONSTANT)
public:
enum FILE_TYPE{
IS_CALL_RECORD,
IS_VOICE_RECORD,
IS_SNAPSHOT,
IS_PLAYABLE,// playable but nor call nor voice
IS_UNKNOWN
};
Q_ENUM(FILE_TYPE)
FileMediaModel(const QString& path, QObject * parent = nullptr);
FileMediaModel(const QFileInfo& fileInfo, QObject * parent = nullptr);
~FileMediaModel();
static QSharedPointer<FileMediaModel> create(const QString& path);
static QSharedPointer<FileMediaModel> create(const QFileInfo& fileInfo);
void init();
QString getBaseName() const;
QString getFilePath() const;
int getDuration() const;
QDateTime getCreationDateTime() const;
QStringList getParsedBaseName() const;
FILE_TYPE getType()const;
QString getFrom()const;
QString getTo()const;
private:
QFileInfo mFileInfo;
int mDuration = -1; // Set by LinphonePlayer when cration an instance of FileModel
FILE_TYPE mType = IS_UNKNOWN;
};
#endif

View file

@ -191,6 +191,17 @@ class ColorListModel : public ProxyListModel {
ADD_COLOR("ma_d_b_fg", "white", "[M] Main disabled button : foreground")
ADD_COLOR("ma_h_b_fg", "white", "[M] Main hovered button : foreground")
ADD_COLOR("ma_p_b_fg", "white", "[M] Main pressed button : foreground")
// Inverse
ADD_COLOR("ma_n_b_inv_bg", "transparent", "[M] Main normal button : inverse background")
ADD_COLOR("ma_d_b_inv_bg", "transparent", "[M] Main disabled button : inverse background")
ADD_COLOR("ma_h_b_inv_bg", "transparent", "[M] Main hovered button : inverse background")
ADD_COLOR("ma_p_b_inv_bg", "transparent", "[M] Main pressed button : inverse background")
ADD_COLOR_WITH_LINK("ma_n_b_inv_fg", "", "[M] Main normal button : inverse foreground", "i")
ADD_COLOR_WITH_LINK("ma_d_b_inv_fg", "", "[M] Main disabled button : inverse foreground", "primary_d")
ADD_COLOR_WITH_LINK("ma_h_b_inv_fg", "", "[M] Main hovered button : inverse foreground", "b")
ADD_COLOR_WITH_LINK("ma_p_b_inv_fg", "", "[M] Main pressed button : inverse foreground", "m")
//-------------------------------------
// Accept Actions : like accepting a call
ADD_COLOR_WITH_LINK("a_n_b_bg", "", "[M] Accept normal button : background", "primary_accept")

View file

@ -64,6 +64,23 @@ QString RecorderModel::getFile()const{
return Utils::coreStringToAppString(mRecorder->getFile());
}
QStringList RecorderModel::splitSavedFilename(const QString& filename){
QStringList fields = filename.split('_');
if(fields.size() == 3 && fields[0] == "vocal" && fields[1].split('-').size() == 3
&& fields[2].split('-').size() == 4){
return fields;
}else
return QStringList(filename);
}
QDateTime RecorderModel::getDateTimeSavedFilename(const QString& filename){
auto fields = splitSavedFilename(filename);
if(fields.size() > 1)
return QDateTime::fromString(fields[1] + "_" +fields[2], "yyyy-MM-dd_hh-mm-ss-zzz");
else
return QDateTime();;
}
void RecorderModel::start(){
bool soFarSoGood;
QString filename = QStringLiteral("vocal_%1.mkv")

View file

@ -44,6 +44,9 @@ public:
LinphoneEnums::RecorderState getState() const;
Q_INVOKABLE QString getFile()const;
static QStringList splitSavedFilename(const QString& filename);// If doesn't match to generateSavedFilename, return filename
static QDateTime getDateTimeSavedFilename(const QString& filename);
Q_INVOKABLE void start();
Q_INVOKABLE void pause();
Q_INVOKABLE void stop();

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "app/App.hpp"
#include "components/core/CoreManager.hpp"
#include "components/file/FileMediaModel.hpp"
#include "components/settings/AccountSettingsModel.hpp"
#include "components/settings/SettingsModel.hpp"
#include "components/sip-addresses/SipAddressesModel.hpp"
#include "utils/Utils.hpp"
#include "RecordingListModel.hpp"
#include <QDebug>
#include <QQmlApplicationEngine>
// =============================================================================
RecordingListModel::RecordingListModel (QObject *parent) : ProxyListModel(parent) {
load();
}
RecordingListModel::~RecordingListModel(){
mList.clear();
}
// -----------------------------------------------------------------------------
void RecordingListModel::load(){
resetData();
QString folder = CoreManager::getInstance()->getSettingsModel()->getSavedCallsFolder();
qInfo() << "[Recordings] looking for recordings in " << CoreManager::getInstance()->getSettingsModel()->getSavedCallsFolder();
QDir dir( folder );
QList<QSharedPointer<FileMediaModel>> files;
foreach(QFileInfo file, dir.entryInfoList(QDir::Files | QDir::NoDotAndDotDot)) {
auto recording = FileMediaModel::create(file);
if(recording) {
App::getInstance()->getEngine()->setObjectOwnership(recording.get(), QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
files << recording;
}
}
if(files.size() > 0)
add<FileMediaModel>(files);
}
QHash<int, QByteArray> RecordingListModel::roleNames () const {
QHash<int, QByteArray> roles = ProxyListModel::roleNames();
roles[Qt::DisplayRole+1] = "$sectionDate";
return roles;
}
QVariant RecordingListModel::data (const QModelIndex &index, int role) const{
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count())
return QVariant();
if(role == Qt::DisplayRole +1){
return QVariant::fromValue(mList[row].objectCast<FileMediaModel>()->getCreationDateTime().date());
}else
return ProxyListModel::data(index, role);
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RECORDING_LIST_MODEL_H_
#define RECORDING_LIST_MODEL_H_
#include "app/proxyModel/ProxyListModel.hpp"
#include "components/sound-player/SoundPlayer.hpp"
// =============================================================================
class RecordingListModel : public ProxyListModel {
Q_OBJECT
public:
RecordingListModel (QObject *parent = Q_NULLPTR);
virtual ~RecordingListModel();
void load();
QHash<int, QByteArray> roleNames () const override;
virtual QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override;
/*
Q_INVOKABLE void remove (SoundPlayer *player);
Q_INVOKABLE SoundPlayer* getSoundPlayer() const;
*/
};
#endif

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "RecordingProxyModel.hpp"
#include "components/core/CoreManager.hpp"
#include "components/settings/AccountSettingsModel.hpp"
#include "components/sip-addresses/SipAddressesModel.hpp"
#include "components/conference/ConferenceModel.hpp"
#include "components/conferenceInfo/ConferenceInfoModel.hpp"
#include "components/file/FileMediaModel.hpp"
#include "components/sound-player/SoundPlayer.hpp"
#include "utils/Utils.hpp"
#include "RecordingListModel.hpp"
#include <QDebug>
// =============================================================================
// -----------------------------------------------------------------------------
RecordingProxyModel::RecordingProxyModel (QObject *parent) : SortFilterProxyModel(parent) {
auto list = new RecordingListModel(this);
setSourceModel(list);
sort(0);
}
// -----------------------------------------------------------------------------
void RecordingProxyModel::remove(FileMediaModel * fileModel){
QFile file(fileModel->getFilePath());
if(file.remove())
qobject_cast<RecordingListModel*>(sourceModel())->remove(fileModel);
}
bool RecordingProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
const FileMediaModel* a = sourceModel()->data(left).value<FileMediaModel*>();
const FileMediaModel* b = sourceModel()->data(right).value<FileMediaModel*>();
return a->getCreationDateTime() > b->getCreationDateTime();
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RECORDING_PROXY_MODEL_H_
#define RECORDING_PROXY_MODEL_H_
#include "app/proxyModel/SortFilterProxyModel.hpp"
#include <memory>
// =============================================================================
class FileMediaModel;
class RecordingProxyModel : public SortFilterProxyModel {
Q_OBJECT
public:
RecordingProxyModel ( QObject *parent = Q_NULLPTR);
//bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan (const QModelIndex &left, const QModelIndex &right) const override;
Q_INVOKABLE void remove(FileMediaModel * file);
//Q_INVOKABLE int getCount() const;
};
#endif

View file

@ -93,26 +93,33 @@ void SoundPlayer::pause () {
emit playbackStateChanged(mPlaybackState);
}
void SoundPlayer::play () {
if (mPlaybackState == SoundPlayer::PlayingState || mSource == "")
return;
bool SoundPlayer::open(){
return mInternalPlayer->open(Utils::appStringToCoreString(mSource)) == 0;
}
bool SoundPlayer::play () {
if (mPlaybackState == SoundPlayer::PlayingState)
return true;
else if(mSource == "")
return false;
if (
(mPlaybackState == SoundPlayer::StoppedState || mPlaybackState == SoundPlayer::ErrorState) &&
mInternalPlayer->open(Utils::appStringToCoreString(mSource))
!open()
) {
qWarning() << QStringLiteral("Unable to open: `%1`").arg(mSource);
return;
return false;
}
if (mInternalPlayer->start()
) {
setError(QStringLiteral("Unable to play: `%1`").arg(mSource));
return;
return false;
}
mForceCloseTimer->start();
mPlaybackState = SoundPlayer::PlayingState;
emit playing();
emit playbackStateChanged(mPlaybackState);
return true;
}
void SoundPlayer::stop () {
@ -131,6 +138,10 @@ int SoundPlayer::getPosition () const {
return mInternalPlayer->getCurrentPosition();
}
bool SoundPlayer::hasVideo() const{
return mInternalPlayer->getIsVideoAvailable();
}
// -----------------------------------------------------------------------------
void SoundPlayer::buildInternalPlayer () {
@ -229,3 +240,17 @@ void SoundPlayer::setPlaybackState (PlaybackState playbackState) {
int SoundPlayer::getDuration () const {
return mInternalPlayer->getDuration();
}
QDateTime SoundPlayer::getCreationDateTime() const{
QFileInfo fileInfo(mSource);
QDateTime creationDate = fileInfo.birthTime();
return creationDate.isValid() ? creationDate : fileInfo.lastModified();
}
QString SoundPlayer::getBaseName() const{
return QFileInfo(mSource).baseName();
}
std::shared_ptr<linphone::Player> SoundPlayer::getLinphonePlayer()const{
return mInternalPlayer;
}

View file

@ -40,9 +40,11 @@ class SoundPlayer : public QObject {
Q_OBJECT
Q_PROPERTY(QString source READ getSource WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QString baseName READ getBaseName NOTIFY sourceChanged)
Q_PROPERTY(PlaybackState playbackState READ getPlaybackState WRITE setPlaybackState NOTIFY playbackStateChanged)
Q_PROPERTY(int duration READ getDuration NOTIFY sourceChanged)
Q_PROPERTY(bool isRinger MEMBER mIsRinger)
Q_PROPERTY(QDateTime creationDateTime READ getCreationDateTime NOTIFY sourceChanged)
public:
enum PlaybackState {
@ -56,13 +58,23 @@ public:
SoundPlayer (QObject *parent = Q_NULLPTR);
~SoundPlayer ();
bool open();
Q_INVOKABLE void pause ();
Q_INVOKABLE void play ();
Q_INVOKABLE bool play ();
Q_INVOKABLE void stop ();
Q_INVOKABLE void seek (int offset);
Q_INVOKABLE int getPosition () const;
Q_INVOKABLE bool hasVideo() const;// Call it after playing a video because the detection is not outside this scope.
int getDuration () const;
QDateTime getCreationDateTime() const;
QString getBaseName() const;
std::shared_ptr<linphone::Player> getLinphonePlayer()const;
QString getSource () const;
void setSource (const QString &source);
signals:
void sourceChanged (const QString &source);
@ -83,13 +95,10 @@ private:
void setError (const QString &message);
QString getSource () const;
void setSource (const QString &source);
PlaybackState getPlaybackState () const;
void setPlaybackState (PlaybackState playbackState);
int getDuration () const;
QString mSource;
PlaybackState mPlaybackState = StoppedState;

View file

@ -18,6 +18,7 @@ Item {
property bool isCameraFromDevice: true
property ParticipantDeviceModel currentDevice
property CallModel callModel
property SoundPlayer linphonePlayer
property bool isPreview: (!callModel && !container.currentDevice) || ( container.currentDevice && container.currentDevice.isMe)
property bool isFullscreen: false
property bool hideCamera: false
@ -77,6 +78,7 @@ Item {
id: camera
Camera {
participantDeviceModel: container.currentDevice
linphonePlayer: container.linphonePlayer
call: container.isCameraFromDevice ? null : container.callModel
anchors.fill: parent
isPreview: container.isPreview

View file

@ -15,6 +15,7 @@ Item{
id: mainItem
property alias currentDevice: camera.currentDevice
property alias callModel: camera.callModel
property alias linphonePlayer : camera.linphonePlayer
property alias hideCamera: camera.hideCamera
property alias isPaused: camera.isPaused
property alias isPreview: camera.isPreview

View file

@ -75,7 +75,6 @@ ApplicationWindow {
readonly property alias conferencesEntry: conferencesEntry
readonly property alias contentLoader: contentLoader
//readonly property alias conferencesEntry: conferencesEntry
readonly property alias menu: menu
readonly property alias timeline: timeline
@ -266,6 +265,10 @@ ApplicationWindow {
onClicked: toggled ? menuBar.close() : menuBar.open()// a bit useless as Menu will depopup on losing focus but this code is kept for giving idea
MainWindowMenuBar {
id: menuBar
onDisplayRecordings: {
timeline.model.unselectAll()
setView('Recordings')
}
}
}
}
@ -325,6 +328,7 @@ ApplicationWindow {
}
}
ApplicationMenuEntry {
id: conferencesEntry
@ -413,7 +417,12 @@ ApplicationWindow {
Loader{
id: customMenuBar
active:Qt.platform.os === 'osx'
sourceComponent:MainWindowTopMenuBar{}
sourceComponent:MainWindowTopMenuBar{
onDisplayRecordings: {
timeline.model.unselectAll()
setView('Recordings')
}
}
}
Component.onCompleted: if(Qt.platform.os === 'osx') menuBar = customMenuBar
// ---------------------------------------------------------------------------

View file

@ -18,6 +18,8 @@ Item {
menu.close()
}
signal displayRecordings()
// ---------------------------------------------------------------------------
// Shortcuts.
// ---------------------------------------------------------------------------
@ -50,6 +52,11 @@ Item {
}
}
Shortcut {
id: recordingsShortcut
onActivated: menuParent.displayRecordings()
}
// ---------------------------------------------------------------------------
// Menu.
// ---------------------------------------------------------------------------
@ -64,6 +71,12 @@ Item {
onTriggered: settingsShortcut.onActivated()
}
MenuItem{
//: 'Recordings' : Label for the recordings menu.
text: qsTr('recordings')
onTriggered: recordingsShortcut.onActivated()
}
MenuItem {
visible: CoreManager.initialized && SettingsModel.isCheckForUpdateAvailable()
//: 'Check for updates' : Item menu for checking updates

View file

@ -6,10 +6,12 @@ import Linphone 1.0
// =============================================================================
MenuBar {
id: menuBar
function open () {
menu.open()
}
signal displayRecordings()
// ---------------------------------------------------------------------------
// Menu.
// ---------------------------------------------------------------------------
@ -25,6 +27,12 @@ MenuBar {
shortcut: StandardKey.Preferences
}
MenuItem {
//: 'Recordings' : Label for the recordings menu.
text: qsTr('recordings')
role: MenuItem.ApplicationSpecificRole
onTriggered: menuBar.displayRecordings()
}
MenuItem {
visible: CoreManager.initialized && SettingsModel.isCheckForUpdateAvailable()

View file

@ -0,0 +1,291 @@
import QtQuick 2.7
import QtQuick.Controls 2.7
import QtQuick.Layouts 1.10
import App 1.0
import Common 1.0
import Linphone 1.0
import Utils 1.0
import UtilsCpp 1.0
import ColorsList 1.0
import Units 1.0
import App.Styles 1.0
// =============================================================================
Item {
Item{
id: mainItem
anchors.fill: parent
anchors.topMargin : 15
anchors.bottomMargin : 15
anchors.leftMargin : 15
anchors.rightMargin : 0
Text {
id: noRec
anchors.centerIn: parent
//: 'No recordings' : Title of an empty list of records.
text: qsTr('titleNoRecordings')
visible : recordingsProxyModel.count === 0
color: RecordingsStyle.title.color
font.pointSize: RecordingsStyle.title.pointSize
}
Component {
id: sectionHeading
Form {
anchors.rightMargin : 10
required property string section
title: section
width: parent.width
height: 30
}
}
ScrollableListView {
anchors.fill: parent
id: recordingsList
spacing: 0
model: RecordingProxyModel {
id: recordingsProxyModel
}
section.property: '$sectionDate'
section.criteria: ViewSection.FullString
section.delegate: sectionHeading
delegate: Loader{
id: lineLoader
property bool isMedia: $modelData && ($modelData.type != FileMediaModel.IS_UNKNOWN && $modelData.type != FileMediaModel.IS_SNAPSHOT) // Test only extension because file can be encrypted.
property string title: ($modelData.type == FileMediaModel.IS_CALL_RECORD || $modelData.type == FileMediaModel.IS_SNAPSHOT
? $modelData.from + ' => ' +$modelData.to
: $modelData.type == FileMediaModel.IS_VOICE_RECORD
//: 'Vocal' : Label for recording type that is a vocal message.
? qsTr('recordingsVocalLabel')
: $modelData.baseName)
+ ' - ' +UtilsCpp.toTimeString($modelData.creationDateTime)
sourceComponent: isMedia ? mediaComponent : fileComponent
//--------------------------------------------------------------------------
// MEDIA
//--------------------------------------------------------------------------
Component{
id: mediaComponent
RowLayout {
id: lineItem
width: recordingsList.width
property bool isPlaying : vocalPlayer.item && vocalPlayer.item.playbackState === SoundPlayer.PlayingState
onIsPlayingChanged: {
if (isPlaying) {
if(mediaProgressBar.value >= 100)
mediaProgressBar.value = 0
timer.start()
} else {
timer.stop()
}
}
Loader{
id: vocalPlayer
active: false
sourceComponent: SoundPlayer {
id: player
source: $modelData.filePath
onStopped: {
mediaProgressBar.value = 101
videoView.linphonePlayer = null
}
Component.onCompleted: {
mediaProgressBar.value = 0
play()
videoView.linphonePlayer = null
if( player.hasVideo())
videoView.linphonePlayer = player
}
}
}
ActionButton {
Layout.alignment: Qt.AlignRight
isCustom: true
colorSet: lineItem.isPlaying ? RecordingsStyle.buttons.pause : RecordingsStyle.buttons.play
onClicked: {
if(!vocalPlayer.active)
vocalPlayer.active = true
else if(lineItem.isPlaying){// Pause the play
vocalPlayer.item.pause()
}else{// Play the audio
vocalPlayer.item.play()
videoView.linphonePlayer = null
if(vocalPlayer.item.hasVideo())
videoView.linphonePlayer = vocalPlayer.item
}
}
}
ColumnLayout {
Layout.rightMargin : 15
Layout.leftMargin : 30
Layout.fillWidth: true
spacing:0
RowLayout {
Layout.fillWidth: true
Layout.topMargin: 10
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: lineLoader.title
horizontalAlignment: Text.AlignLeft
font.pointSize: RecordingsStyle.filename.pointSize
color: RecordingsStyle.filename.color
}
Text {
id: durationText
Layout.fillWidth: true
Layout.alignment: Qt.AlignRight
text: (vocalPlayer.item ? Utils.formatElapsedTime(vocalPlayer.item.getPosition()/1000) + "/" : '')
+Utils.formatElapsedTime($modelData.duration/1000)
horizontalAlignment: Text.AlignRight
font.pointSize: RecordingsStyle.filename.pointSize
color: RecordingsStyle.filename.color
}
}
Slider {
id: mediaProgressBar
Layout.fillWidth: true
Layout.leftMargin: 20
enabled: true
to: 101
value: vocalPlayer.item ? 0.01 * progressDuration / 5 : 0
Timer{
id: timer
repeat: true
onTriggered: {
if( vocalPlayer.item){
mediaProgressBar.value = 100 * ( vocalPlayer.item.getPosition() / vocalPlayer.item.duration)
durationText.text = Utils.formatElapsedTime(vocalPlayer.item.getPosition()/1000) + "/" + Utils.formatElapsedTime(vocalPlayer.item.duration/1000)
}
}
interval: 5
}
onValueChanged:{
if(value > 100){
timer.stop()
durationText.text = Utils.formatElapsedTime(0) + "/" + Utils.formatElapsedTime(vocalPlayer.item.duration/1000)
if(vocalPlayer.item)
vocalPlayer.item.stop()
value = 0
}
}
onMoved: if(vocalPlayer.item){
vocalPlayer.item.seek(vocalPlayer.item.duration*value / 100)
value = 100 * (vocalPlayer.item.getPosition() / vocalPlayer.item.duration)
}
}
}
ActionButton {
Layout.rightMargin : 30
Layout.leftMargin : 15
isCustom: true
backgroundRadius: width/2
colorSet: RecordingsStyle.buttons.remove
onClicked: {
window.detachVirtualWindow()
window.attachVirtualWindow(Utils.buildCommonDialogUri('ConfirmDialog'), {
//: 'Are you sure you want to delete this item?' : Confirmation message for removing a record.
descriptionText: qsTr('recordingsDelete'),
}, function (status) {
if (status) {
recordingsProxyModel.remove($modelData)
}
})
}
}
}
}
//--------------------------------------------------------------------------
// FILE
//--------------------------------------------------------------------------
Component{
id: fileComponent
RowLayout{
width: recordingsList.width
Item{
height: RecordingsStyle.buttons.size
width: height
ActionButton{
anchors.centerIn: parent
isCustom: true
backgroundRadius: width/2
colorSet: $modelData.type == FileMediaModel.IS_SNAPSHOT ? RecordingsStyle.buttons.openImage : RecordingsStyle.buttons.openFile
onClicked: {
Qt.openUrlExternally(Utils.getUriFromSystemPath($modelData.filePath))
}
}
}
Text{
Layout.fillWidth: true
Layout.leftMargin : 30
text: lineLoader.title
font.pointSize: RecordingsStyle.filename.pointSize
color: RecordingsStyle.filename.color
}
ActionButton {
Layout.rightMargin : 30
Layout.leftMargin : 15
isCustom: true
backgroundRadius: width/2
colorSet: RecordingsStyle.buttons.remove
onClicked: {
window.detachVirtualWindow()
window.attachVirtualWindow(Utils.buildCommonDialogUri('ConfirmDialog'), {
//: 'Are you sure you want to delete this item?' : Confirmation message for removing a record.
descriptionText: qsTr('recordingsDelete'),
}, function (status) {
if (status) {
recordingsProxyModel.remove($modelData)
}
})
}
}
}
}
}// Loader
}
Item{
id: videoViewItem
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
height: 200
width: height * 16/9
visible: videoView.active
Loader{
id: videoView
property SoundPlayer linphonePlayer
anchors.fill: parent
active: linphonePlayer
sourceComponent: Component{
CameraView{
isPreview: false
linphonePlayer: videoView.linphonePlayer
}
}
}
MovableMouseArea{
id: dragger
anchors.fill: parent
function resetPosition(){
videoViewItem.anchors.bottom = mainItem.bottom
videoViewItem.anchors.horizontalCenter = mainItem.horizontalCenter
}
onVisibleChanged: if(!visible){
resetPosition()
}
drag.target: videoViewItem
onDraggingChanged: if(dragging){
videoViewItem.anchors.bottom = undefined
videoViewItem.anchors.horizontalCenter = undefined
}
onRequestResetPosition: resetPosition()
}
}
}
}

View file

@ -50,6 +50,12 @@ QtObject {
property color color: ColorsList.add(sectionName+'_me_conferences', 'me_n_b_inv_fg').color
property color selectedColor: ColorsList.add(sectionName+'_me_conferences_c', 'me_p_b_inv_fg').color
}
property QtObject recordings: QtObject {
property string icon: 'recordings_custom'
property int iconSize: 50
property color color: ColorsList.add(sectionName+'_me_recordings', 'me_n_b_inv_fg').color
property color selectedColor: ColorsList.add(sectionName+'_me_recordings_c', 'me_p_b_inv_fg').color
}
/*
property string conferencesIcon: 'conference'
property color conferencesColor: ColorsList.add(sectionName+'_me_confs', 'me_n_b_inv_fg').color

View file

@ -0,0 +1,80 @@
pragma Singleton
import QtQuick 2.7
import Units 1.0
import ColorsList 1.0
// =============================================================================
QtObject {
property string sectionName : 'Recordings'
property QtObject title: QtObject {
property color color: ColorsList.add(sectionName+'_title', 'j').color
property int pointSize: Units.dp * 12
}
property QtObject filename: QtObject {
property color color: ColorsList.add(sectionName+'_filename', 'j').color
property int pointSize: Units.dp * 10
}
property QtObject buttons: QtObject {
property int size: 40
property QtObject play: QtObject {
property int iconSize: buttons.size
property string name : 'play'
property string icon : 'chat_audio_play_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'ma_n_b_inv_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'ma_h_b_inv_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'ma_p_b_inv_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'ma_n_b_inv_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'ma_h_b_inv_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'ma_p_b_inv_fg').color
}
property QtObject pause: QtObject {
property int iconSize: buttons.size
property string name : 'pause'
property string icon : 'chat_audio_pause_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'ma_n_b_inv_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'ma_h_b_inv_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'ma_p_b_inv_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'ma_n_b_inv_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'ma_h_b_inv_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'ma_p_b_inv_fg').color
}
property QtObject remove: QtObject {
property int iconSize: buttons.size
property string name : 'delete'
property string icon : 'delete_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'me_p_b_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'me_n_b_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_fg').color
}
property QtObject openImage: QtObject {
property int iconSize: buttons.size/2
property string name : 'openImage'
property string icon : 'file_unknown_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'ma_n_b_inv_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'ma_h_b_inv_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'ma_p_b_inv_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'ma_n_b_inv_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'ma_h_b_inv_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'ma_p_b_inv_fg').color
}
property QtObject openFile: QtObject {
property int iconSize: buttons.size/2
property string name : 'openFile'
property string icon : 'file_extension_custom'
property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'ma_n_b_inv_bg').color
property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'ma_h_b_inv_bg').color
property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'ma_p_b_inv_bg').color
property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'ma_n_b_inv_fg').color
property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'ma_h_b_inv_fg').color
property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'ma_p_b_inv_fg').color
}
}
}

View file

@ -40,6 +40,7 @@ singleton HomeStyle 1.0 Main/HomeStyle.qml
singleton HistoryViewStyle 1.0 Main/HistoryViewStyle.qml
singleton InviteFriendsStyle 1.0 Main/InviteFriendsStyle.qml
singleton MainWindowStyle 1.0 Main/MainWindowStyle.qml
singleton RecordingsStyle 1.0 Main/RecordingsStyle.qml
singleton AboutStyle 1.0 Main/Dialogs/AboutStyle.qml
singleton AuthenticationRequestStyle 1.0 Main/Dialogs/AuthenticationRequestStyle.qml

@ -1 +1 @@
Subproject commit e57d22dca56af8ecd9b0ee5b8e072dbdd65d2266
Subproject commit d6c7561ffaecb43bce24e5f34c1a8e023fde6503